javatest: manage environment also for non-java dependencies (ie. JNI / JEP)

This commit is contained in:
Federico Pellegrin 2019-09-13 22:24:28 +00:00 committed by ita1024
parent 68bf3867df
commit 02c3711e2e
7 changed files with 274 additions and 10 deletions

View File

@ -0,0 +1,27 @@
public final class StringUtils
{
public static final String LIBRARY_NAME = "stringUtils";
static
{
System.loadLibrary(LIBRARY_NAME);
}
private StringUtils()
{
}
public static native boolean isAlpha(String string);
public static native boolean isEmpty(String string);
public static void main(String[] args)
{
System.out.println(StringUtils.isAlpha("sureIs"));
System.out.println(StringUtils.isAlpha("nope!"));
System.out.println(StringUtils.isEmpty(" "));
System.out.println(StringUtils.isEmpty("nope"));
}
}

View File

@ -0,0 +1,29 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class StringUtils */
#ifndef _Included_StringUtils
#define _Included_StringUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: StringUtils
* Method: isAlpha
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_StringUtils_isAlpha
(JNIEnv *, jclass, jstring);
/*
* Class: StringUtils
* Method: isEmpty
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_StringUtils_isEmpty
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,51 @@
#include "StringUtils.h"
#include <string.h>
#include <ctype.h>
JNIEXPORT jboolean JNICALL Java_StringUtils_isAlpha(JNIEnv *env, jclass clazz,
jstring jStr)
{
jboolean ret = JNI_TRUE;
char *sp = NULL, *s = NULL;
if (!jStr)
return JNI_FALSE;
s = (char*)(*env)->GetStringUTFChars(env, jStr, 0);
sp = s + strlen(s);
if (sp <= s)
ret = JNI_FALSE;
do
{
if (!isalpha(*(--sp)))
ret = JNI_FALSE;
}
while (sp > s);
(*env)->ReleaseStringUTFChars(env, jStr, s);
return ret;
}
JNIEXPORT jboolean JNICALL Java_StringUtils_isEmpty(JNIEnv *env, jclass clazz,
jstring jStr)
{
jboolean ret = JNI_TRUE;
char *sp = NULL, *s = NULL;
if (!jStr)
return JNI_TRUE;
s = (char*)(*env)->GetStringUTFChars(env, jStr, 0);
sp = s + strlen(s);
if (sp <= s)
ret = JNI_TRUE;
do
{
if (!isspace(*(--sp)))
ret = JNI_FALSE;
}
while (sp > s);
(*env)->ReleaseStringUTFChars(env, jStr, s);
return ret;
}

View File

@ -0,0 +1,20 @@
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestJni {
@Test
public void testTrue() {
Assert.assertEquals(true, StringUtils.isAlpha("myfootest"), "'myfootest' is alpha");
}
@Test
public void testFalse() {
Assert.assertEquals(false, StringUtils.isAlpha("my f00 t3$t"), "'my f00 t3$t' is not alpha");
}
@Test
public void testIsEmpty() {
Assert.assertEquals(false, StringUtils.isEmpty("emptyNOT"), "'emptyNOT' is not empty");
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="SuiteForJni">
<test name="testJni">
<classes>
<class name="TestJni"/>
</classes>
</test>
</suite>

View File

@ -1,6 +1,6 @@
#! /usr/bin/env python
# encoding: utf-8
# Federico Pellegrin, 2017 (fedepell)
# Federico Pellegrin, 2019 (fedepell)
#
# Simple script to demonstrate integration of Java Unit testing inside
@ -21,9 +21,12 @@ def test_results(bld):
def options(opt):
opt.load('java waf_unit_test javatest')
opt.load('compiler_c')
def configure(conf):
conf.load('java javatest')
conf.load('compiler_c')
conf.check_jni_headers()
def build(bld):
bld(features = 'javac',
@ -39,7 +42,7 @@ def build(bld):
srcdir = 'test/', # folder containing the sources to compile
outdir = 'test', # folder where to output the classes (in the build directory)
sourcepath = ['test'],
classpath = [ 'src' ],
classpath = [ 'src' ],
basedir = 'test', # folder containing the classes and other files to package (must match outdir)
use = ['JAVATEST', 'mainprog'],
ut_str = 'java -cp ${CLASSPATH} ${JTRUNNER} ${SRC}',
@ -49,5 +52,25 @@ def build(bld):
)
# Demonstrate correct handling also of dependency to non-java tasks (see !2257)
bld(name='stjni', features='javac jar', srcdir='jni/java', outdir='jni/java', basedir='jni/java', destfile='stringUtils.jar')
bld.shlib(source = 'jni/jni/source/StringUtils.c',
includes = 'jni/jni/include',
target = 'jni/stringUtils',
uselib = 'JAVA')
bld(features = 'javac javatest',
srcdir = 'jni/test/', # folder containing the sources to compile
outdir = 'jni/test', # folder where to output the classes (in the build directory)
sourcepath = ['jni/test'],
classpath = [ 'jni/src' ],
basedir = 'jni/test', # folder containing the classes and other files to package (must match outdir)
use = ['JAVATEST', 'stjni', 'jni/stringUtils'],
ut_str = 'java -cp ${CLASSPATH} ${JTRUNNER} ${SRC}',
jtest_source = bld.path.ant_glob('jni/test/*.xml'),
)
bld.add_post_fun(test_results)

View File

@ -1,6 +1,6 @@
#! /usr/bin/env python
# encoding: utf-8
# Federico Pellegrin, 2017 (fedepell)
# Federico Pellegrin, 2019 (fedepell)
"""
Provides Java Unit test support using :py:class:`waflib.Tools.waf_unit_test.utest`
@ -11,6 +11,10 @@ standard waf unit test environment. It has been tested with TestNG and JUnit
but should be easily expandable to other frameworks given the flexibility of
ut_str provided by the standard waf unit test environment.
The extra takes care also of managing non-java dependencies (ie. C/C++ libraries
using JNI or Python modules via JEP) and setting up the environment needed to run
them.
Example usage:
def options(opt):
@ -20,15 +24,15 @@ def configure(conf):
conf.load('java javatest')
def build(bld):
[ ... mainprog is built here ... ]
bld(features = 'javac javatest',
srcdir = 'test/',
outdir = 'test',
srcdir = 'test/',
outdir = 'test',
sourcepath = ['test'],
classpath = [ 'src' ],
basedir = 'test',
classpath = [ 'src' ],
basedir = 'test',
use = ['JAVATEST', 'mainprog'], # mainprog is the program being tested in src/
ut_str = 'java -cp ${CLASSPATH} ${JTRUNNER} ${SRC}',
jtest_source = bld.path.ant_glob('test/*.xml'),
@ -53,10 +57,91 @@ The runner class presence on the system is checked for at configuration stage.
"""
import os
from waflib import Task, TaskGen, Options
from waflib import Task, TaskGen, Options, Errors, Utils, Logs
from waflib.Tools import ccroot
def _process_use_rec(self, name):
"""
Recursively process ``use`` for task generator with name ``name``..
Used by javatest_process_use.
"""
if name in self.javatest_use_not or name in self.javatest_use_seen:
return
try:
tg = self.bld.get_tgen_by_name(name)
except Errors.WafError:
self.javatest_use_not.add(name)
return
self.javatest_use_seen.append(name)
tg.post()
for n in self.to_list(getattr(tg, 'use', [])):
_process_use_rec(self, n)
@TaskGen.feature('javatest')
@TaskGen.after_method('apply_java', 'use_javac_files', 'set_classpath')
@TaskGen.after_method('process_source', 'apply_link', 'use_javac_files')
def javatest_process_use(self):
"""
Process the ``use`` attribute which contains a list of task generator names and store
paths that later is used to populate the unit test runtime environment.
"""
self.javatest_use_not = set()
self.javatest_use_seen = []
self.javatest_libpaths = [] # strings or Nodes
self.javatest_pypaths = [] # strings or Nodes
self.javatest_dep_nodes = []
names = self.to_list(getattr(self, 'use', []))
for name in names:
_process_use_rec(self, name)
def extend_unique(lst, varlst):
ext = []
for x in varlst:
if x not in lst:
ext.append(x)
lst.extend(ext)
# Collect type specific info needed to construct a valid runtime environment
# for the test.
for name in self.javatest_use_seen:
tg = self.bld.get_tgen_by_name(name)
# Python-Java embedding crosstools such as JEP
if 'py' in tg.features:
# Python dependencies are added to PYTHONPATH
pypath = getattr(tg, 'install_from', tg.path)
if 'buildcopy' in tg.features:
# Since buildcopy is used we assume that PYTHONPATH in build should be used,
# not source
extend_unique(self.javatest_pypaths, [pypath.get_bld().abspath()])
# Add buildcopy output nodes to dependencies
extend_unique(self.javatest_dep_nodes, [o for task in getattr(tg, 'tasks', []) for o in getattr(task, 'outputs', [])])
else:
# If buildcopy is not used, depend on sources instead
extend_unique(self.javatest_dep_nodes, tg.source)
extend_unique(self.javatest_pypaths, [pypath.abspath()])
if getattr(tg, 'link_task', None):
# For tasks with a link_task (C, C++, D et.c.) include their library paths:
if not isinstance(tg.link_task, ccroot.stlink_task):
extend_unique(self.javatest_dep_nodes, tg.link_task.outputs)
extend_unique(self.javatest_libpaths, tg.link_task.env.LIBPATH)
if 'pyext' in tg.features:
# If the taskgen is extending Python we also want to add the interpreter libpath.
extend_unique(self.javatest_libpaths, tg.link_task.env.LIBPATH_PYEXT)
else:
# Only add to libpath if the link task is not a Python extension
extend_unique(self.javatest_libpaths, [tg.link_task.outputs[0].parent.abspath()])
@TaskGen.feature('javatest')
@TaskGen.after_method('apply_java', 'use_javac_files', 'set_classpath', 'javatest_process_use')
def make_javatest(self):
"""
Creates a ``utest`` task with a populated environment for Java Unit test execution
@ -65,6 +150,9 @@ def make_javatest(self):
tsk = self.create_task('utest')
tsk.set_run_after(self.javac_task)
# Dependencies from recursive use analysis
tsk.dep_nodes.extend(self.javatest_dep_nodes)
# Put test input files as waf_unit_test relies on that for some prints and log generation
# If jtest_source is there, this is specially useful for passing XML for TestNG
# that contain test specification, use that as inputs, otherwise test sources
@ -97,6 +185,21 @@ def make_javatest(self):
if not hasattr(self, 'ut_env'):
self.ut_env = dict(os.environ)
def add_paths(var, lst):
# Add list of paths to a variable, lst can contain strings or nodes
lst = [ str(n) for n in lst ]
Logs.debug("ut: %s: Adding paths %s=%s", self, var, lst)
self.ut_env[var] = os.pathsep.join(lst) + os.pathsep + self.ut_env.get(var, '')
add_paths('PYTHONPATH', self.javatest_pypaths)
if Utils.is_win32:
add_paths('PATH', self.javatest_libpaths)
elif Utils.unversioned_sys_platform() == 'darwin':
add_paths('DYLD_LIBRARY_PATH', self.javatest_libpaths)
add_paths('LD_LIBRARY_PATH', self.javatest_libpaths)
else:
add_paths('LD_LIBRARY_PATH', self.javatest_libpaths)
def configure(ctx):
cp = ctx.env.CLASSPATH or '.'