waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

javatest.py (8367B)


      1 #! /usr/bin/env python
      2 # encoding: utf-8
      3 # Federico Pellegrin, 2019 (fedepell)
      4 
      5 """
      6 Provides Java Unit test support using :py:class:`waflib.Tools.waf_unit_test.utest`
      7 task via the **javatest** feature.
      8 
      9 This gives the possibility to run unit test and have them integrated into the
     10 standard waf unit test environment. It has been tested with TestNG and JUnit
     11 but should be easily expandable to other frameworks given the flexibility of
     12 ut_str provided by the standard waf unit test environment.
     13 
     14 The extra takes care also of managing non-java dependencies (ie. C/C++ libraries
     15 using JNI or Python modules via JEP) and setting up the environment needed to run
     16 them.
     17 
     18 Example usage:
     19 
     20 def options(opt):
     21 	opt.load('java waf_unit_test javatest')
     22 
     23 def configure(conf):
     24 	conf.load('java javatest')
     25 
     26 def build(bld):
     27 
     28 	[ ... mainprog is built here ... ]
     29 
     30 	bld(features = 'javac javatest',
     31 		srcdir     = 'test/',
     32 		outdir     = 'test',
     33 		sourcepath = ['test'],
     34 		classpath  = [ 'src' ],
     35 		basedir    = 'test',
     36 		use = ['JAVATEST', 'mainprog'], # mainprog is the program being tested in src/
     37 		ut_str = 'java -cp ${CLASSPATH} ${JTRUNNER} ${SRC}',
     38 		jtest_source = bld.path.ant_glob('test/*.xml'),
     39 	)
     40 
     41 
     42 At command line the CLASSPATH where to find the testing environment and the
     43 test runner (default TestNG) that will then be seen in the environment as
     44 CLASSPATH_JAVATEST (then used for use) and JTRUNNER and can be used for
     45 dependencies and ut_str generation.
     46 
     47 Example configure for TestNG:
     48 	waf configure --jtpath=/tmp/testng-6.12.jar:/tmp/jcommander-1.71.jar --jtrunner=org.testng.TestNG
     49 		 or as default runner is TestNG:
     50 	waf configure --jtpath=/tmp/testng-6.12.jar:/tmp/jcommander-1.71.jar
     51 
     52 Example configure for JUnit:
     53 	waf configure --jtpath=/tmp/junit.jar --jtrunner=org.junit.runner.JUnitCore
     54 
     55 The runner class presence on the system is checked for at configuration stage.
     56 
     57 """
     58 
     59 import os
     60 from waflib import Task, TaskGen, Options, Errors, Utils, Logs
     61 from waflib.Tools import ccroot
     62 
     63 JAR_RE = '**/*'
     64 
     65 def _process_use_rec(self, name):
     66 	"""
     67 	Recursively process ``use`` for task generator with name ``name``..
     68 	Used by javatest_process_use.
     69 	"""
     70 	if name in self.javatest_use_not or name in self.javatest_use_seen:
     71 		return
     72 	try:
     73 		tg = self.bld.get_tgen_by_name(name)
     74 	except Errors.WafError:
     75 		self.javatest_use_not.add(name)
     76 		return
     77 
     78 	self.javatest_use_seen.append(name)
     79 	tg.post()
     80 
     81 	for n in self.to_list(getattr(tg, 'use', [])):
     82 		_process_use_rec(self, n)
     83 
     84 @TaskGen.feature('javatest')
     85 @TaskGen.after_method('process_source', 'apply_link', 'use_javac_files')
     86 def javatest_process_use(self):
     87 	"""
     88 	Process the ``use`` attribute which contains a list of task generator names and store
     89 	paths that later is used to populate the unit test runtime environment.
     90 	"""
     91 	self.javatest_use_not = set()
     92 	self.javatest_use_seen = []
     93 	self.javatest_libpaths = [] # strings or Nodes
     94 	self.javatest_pypaths = [] # strings or Nodes
     95 	self.javatest_dep_nodes = []
     96 
     97 	names = self.to_list(getattr(self, 'use', []))
     98 	for name in names:
     99 		_process_use_rec(self, name)
    100 
    101 	def extend_unique(lst, varlst):
    102 		ext = []
    103 		for x in varlst:
    104 			if x not in lst:
    105 				ext.append(x)
    106 		lst.extend(ext)
    107 
    108 	# Collect type specific info needed to construct a valid runtime environment
    109 	# for the test.
    110 	for name in self.javatest_use_seen:
    111 		tg = self.bld.get_tgen_by_name(name)
    112 
    113 		# Python-Java embedding crosstools such as JEP
    114 		if 'py' in tg.features:
    115 			# Python dependencies are added to PYTHONPATH
    116 			pypath = getattr(tg, 'install_from', tg.path)
    117 
    118 			if 'buildcopy' in tg.features:
    119 				# Since buildcopy is used we assume that PYTHONPATH in build should be used,
    120 				# not source
    121 				extend_unique(self.javatest_pypaths, [pypath.get_bld().abspath()])
    122 
    123 				# Add buildcopy output nodes to dependencies
    124 				extend_unique(self.javatest_dep_nodes, [o for task in getattr(tg, 'tasks', []) for o in getattr(task, 'outputs', [])])
    125 			else:
    126 				# If buildcopy is not used, depend on sources instead
    127 				extend_unique(self.javatest_dep_nodes, tg.source)
    128 				extend_unique(self.javatest_pypaths, [pypath.abspath()])
    129 
    130 
    131 		if getattr(tg, 'link_task', None):
    132 			# For tasks with a link_task (C, C++, D et.c.) include their library paths:
    133 			if not isinstance(tg.link_task, ccroot.stlink_task):
    134 				extend_unique(self.javatest_dep_nodes, tg.link_task.outputs)
    135 				extend_unique(self.javatest_libpaths, tg.link_task.env.LIBPATH)
    136 
    137 				if 'pyext' in tg.features:
    138 					# If the taskgen is extending Python we also want to add the interpreter libpath.
    139 					extend_unique(self.javatest_libpaths, tg.link_task.env.LIBPATH_PYEXT)
    140 				else:
    141 					# Only add to libpath if the link task is not a Python extension
    142 					extend_unique(self.javatest_libpaths, [tg.link_task.outputs[0].parent.abspath()])
    143 
    144 		if 'javac' in tg.features or 'jar' in tg.features:
    145 			if hasattr(tg, 'jar_task'):
    146 				# For Java JAR tasks depend on generated JAR
    147 				extend_unique(self.javatest_dep_nodes, tg.jar_task.outputs)
    148 			else:
    149 				# For Java non-JAR ones we need to glob generated files (Java output files are not predictable)
    150 				if hasattr(tg, 'outdir'):
    151 					base_node = tg.outdir
    152 				else:
    153 					base_node = tg.path.get_bld()
    154 
    155 				self.javatest_dep_nodes.extend([dx for dx in base_node.ant_glob(JAR_RE, remove=False, quiet=True)])
    156 
    157 
    158 
    159 @TaskGen.feature('javatest')
    160 @TaskGen.after_method('apply_java', 'use_javac_files', 'set_classpath', 'javatest_process_use')
    161 def make_javatest(self):
    162 	"""
    163 	Creates a ``utest`` task with a populated environment for Java Unit test execution
    164 
    165 	"""
    166 	tsk = self.create_task('utest')
    167 	tsk.set_run_after(self.javac_task)
    168 
    169 	# Dependencies from recursive use analysis
    170 	tsk.dep_nodes.extend(self.javatest_dep_nodes)
    171 
    172 	# Put test input files as waf_unit_test relies on that for some prints and log generation
    173 	# If jtest_source is there, this is specially useful for passing XML for TestNG
    174 	# that contain test specification, use that as inputs, otherwise test sources
    175 	if getattr(self, 'jtest_source', None):
    176 		tsk.inputs = self.to_nodes(self.jtest_source)
    177 	else:
    178 		if self.javac_task.srcdir[0].exists():
    179 			tsk.inputs = self.javac_task.srcdir[0].ant_glob('**/*.java', remove=False)
    180 
    181 	if getattr(self, 'ut_str', None):
    182 		self.ut_run, lst = Task.compile_fun(self.ut_str, shell=getattr(self, 'ut_shell', False))
    183 		tsk.vars = lst + tsk.vars
    184 
    185 	if getattr(self, 'ut_cwd', None):
    186 		if isinstance(self.ut_cwd, str):
    187 			# we want a Node instance
    188 			if os.path.isabs(self.ut_cwd):
    189 				self.ut_cwd = self.bld.root.make_node(self.ut_cwd)
    190 			else:
    191 				self.ut_cwd = self.path.make_node(self.ut_cwd)
    192 	else:
    193 		self.ut_cwd = self.bld.bldnode
    194 
    195 	# Get parent CLASSPATH and add output dir of test, we run from wscript dir
    196 	# We have to change it from list to the standard java -cp format (: separated)
    197 	tsk.env.CLASSPATH = ':'.join(self.env.CLASSPATH) + ':' + self.outdir.abspath()
    198 
    199 	if not self.ut_cwd.exists():
    200 		self.ut_cwd.mkdir()
    201 
    202 	if not hasattr(self, 'ut_env'):
    203 		self.ut_env = dict(os.environ)
    204 		def add_paths(var, lst):
    205 			# Add list of paths to a variable, lst can contain strings or nodes
    206 			lst = [ str(n) for n in lst ]
    207 			Logs.debug("ut: %s: Adding paths %s=%s", self, var, lst)
    208 			self.ut_env[var] = os.pathsep.join(lst) + os.pathsep + self.ut_env.get(var, '')
    209 
    210 		add_paths('PYTHONPATH', self.javatest_pypaths)
    211 
    212 		if Utils.is_win32:
    213 			add_paths('PATH', self.javatest_libpaths)
    214 		elif Utils.unversioned_sys_platform() == 'darwin':
    215 			add_paths('DYLD_LIBRARY_PATH', self.javatest_libpaths)
    216 			add_paths('LD_LIBRARY_PATH', self.javatest_libpaths)
    217 		else:
    218 			add_paths('LD_LIBRARY_PATH', self.javatest_libpaths)
    219 
    220 def configure(ctx):
    221 	cp = ctx.env.CLASSPATH or '.'
    222 	if getattr(Options.options, 'jtpath', None):
    223 		ctx.env.CLASSPATH_JAVATEST = getattr(Options.options, 'jtpath').split(':')
    224 		cp += ':' + getattr(Options.options, 'jtpath')
    225 
    226 	if getattr(Options.options, 'jtrunner', None):
    227 		ctx.env.JTRUNNER = getattr(Options.options, 'jtrunner')
    228 
    229 	if ctx.check_java_class(ctx.env.JTRUNNER, with_classpath=cp):
    230 		ctx.fatal('Could not run test class %r' % ctx.env.JTRUNNER)
    231 
    232 def options(opt):
    233 	opt.add_option('--jtpath', action='store', default='', dest='jtpath',
    234 		help='Path to jar(s) needed for javatest execution, colon separated, if not in the system CLASSPATH')
    235 	opt.add_option('--jtrunner', action='store', default='org.testng.TestNG', dest='jtrunner',
    236 		help='Class to run javatest test [default: org.testng.TestNG]')
    237