From 0bde347b7ee5d997bfd8cacdf58d89faea2778a8 Mon Sep 17 00:00:00 2001 From: Mick Jordan <mick.jordan@oracle.com> Date: Tue, 15 Nov 2016 11:31:47 -0800 Subject: [PATCH] move junit functionality from mx into mx.fastr --- .../truffle/r/test/FastRJUnitWrapper.java | 155 ++++++++++++++++++ mx.fastr/mx_fastr.py | 22 ++- mx.fastr/mx_fastr_junit.py | 113 +++++++++++++ 3 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/FastRJUnitWrapper.java create mode 100644 mx.fastr/mx_fastr_junit.py diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/FastRJUnitWrapper.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/FastRJUnitWrapper.java new file mode 100644 index 0000000000..4c035b258b --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/FastRJUnitWrapper.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.truffle.r.test; + +import org.junit.internal.*; +import org.junit.runner.*; +import org.junit.runner.notification.*; + +import java.io.*; +import java.util.*; +import java.lang.reflect.*; + +import junit.runner.*; + +public class FastRJUnitWrapper { + // CheckStyle: stop system..print check + + /** + * @param args args[0] is the path where to read the names of the testclasses. + */ + public static void main(String[] args) { + String testsFile = null; + String runListenerClassName = null; + String testClassName = null; + List<Failure> missingClasses = new ArrayList<>(); + int i = 0; + while (i < args.length) { + final String arg = args[i]; + switch (arg) { + case "--testsfile": + testsFile = getNextArg(args, ++i); + break; + case "--runlistener": + runListenerClassName = getNextArg(args, ++i); + break; + case "--testclass": + testClassName = getNextArg(args, ++i); + break; + default: + usage(); + } + i++; + } + RunListener runListener = null; + String runListenerArg = null; + if (runListenerClassName != null) { + int cx = runListenerClassName.indexOf(':'); + if (cx > 0) { + runListenerArg = runListenerClassName.substring(cx + 1); + runListenerClassName = runListenerClassName.substring(0, cx); + } + try { + Class<?> runListenerClass = Class.forName(runListenerClassName); + if (runListenerArg == null) { + runListener = (RunListener) runListenerClass.newInstance(); + } else { + Constructor<?> cons = runListenerClass.getDeclaredConstructor(String.class); + runListener = (RunListener) cons.newInstance(runListenerArg); + } + } catch (Exception ex) { + System.err.println("error instantiating: " + runListenerClassName + ": " + ex); + } + } + if (testsFile == null && testClassName == null) { + usage(); + } + + Class<?>[] classArgs = null; + String[] stringArgs = null; + try { + if (testClassName != null) { + classArgs = new Class<?>[1]; + classArgs[0] = Class.forName(testClassName); + stringArgs = new String[1]; + stringArgs[0] = testClassName; + } else if (testsFile != null) { + + ArrayList<Class<?>> tests = new ArrayList<>(1000); + try (BufferedReader br = new BufferedReader(new FileReader(testsFile))) { + String className; + while ((className = br.readLine()) != null) { + tests.add(Class.forName(className)); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + System.exit(2); + } + + classArgs = new Class<?>[tests.size()]; + tests.toArray(classArgs); + stringArgs = new String[tests.size()]; + for (int j = 0; j < stringArgs.length; j++) { + stringArgs[j] = classArgs[j].getName(); + } + } + } catch (ClassNotFoundException ex) { + System.out.println("Could not find class: " + testClassName); + Description description = Description.createSuiteDescription(testClassName); + Failure failure = new Failure(description, ex); + missingClasses.add(failure); + } + if (classArgs.length == 1) { + System.out.printf("executing junit test now... (%s)\n", classArgs[0]); + } else { + System.out.printf("executing junit tests now... (%d test classes)\n", classArgs.length); + } + // It is very strange that all this boilerplate is necessary to get the same effect as + // JUnitCore.main + JUnitSystem system = new RealSystem(); + System.out.println("JUnit version " + Version.id()); + JUnitCore core = new JUnitCore(); + core.addListener(new TextListener(system)); + core.addListener(runListener); + Result result = core.run(classArgs); + for (Failure each : missingClasses) { + result.getFailures().add(each); + } + System.exit(result.wasSuccessful() ? 0 : 1); + } + + private static String getNextArg(String[] args, int i) { + if (i < args.length) { + return args[i]; + } else { + usage(); + return null; + } + } + + private static void usage() { + System.err.println("usage: [--testsfile file] [--test testclass] [--runlistener runlistenerclass[:arg]]"); + System.exit(1); + } +} diff --git a/mx.fastr/mx_fastr.py b/mx.fastr/mx_fastr.py index 860e12cd49..cb937d8620 100644 --- a/mx.fastr/mx_fastr.py +++ b/mx.fastr/mx_fastr.py @@ -20,13 +20,14 @@ # or visit www.oracle.com if you need additional information or have any # questions. # -import platform, subprocess, sys +import platform, subprocess, sys, shlex from os.path import join, sep from argparse import ArgumentParser import mx import mx_gate import mx_fastr_pkgs import mx_fastr_dists +import mx_fastr_junit from mx_fastr_dists import FastRNativeProject, FastRTestNativeProject, FastRReleaseProject, FastRNativeRecommendedProject #pylint: disable=unused-import import mx_copylib import mx_fastr_mkgramrd @@ -213,6 +214,13 @@ def run_r(args, command, parser=None, extraVmArgs=None, jdk=None, **kwargs): return do_run_r(rargs, command, extraVmArgs=extraVmArgs, jdk=jdk, **kwargs) +def split_j_args(extraVmArgsList): + extraVmArgs = [] + if extraVmArgsList: + for e in extraVmArgsList: + extraVmArgs += [x for x in shlex.split(e.lstrip('@'))] + return extraVmArgs + def rshell(args): '''run R shell''' return run_r(args, 'r') @@ -359,22 +367,22 @@ def junit(args): if os.environ.has_key('R_PROFILE_USER'): mx.abort('unset R_PROFILE_USER before running unit tests') _unset_conflicting_envs() - return mx.junit(args, _junit_r_harness, parser=parser, jdk_default=get_default_jdk()) + return mx_fastr_junit.junit(args, _junit_r_harness, parser=parser, jdk_default=get_default_jdk()) def junit_simple(args): - return mx.command_function('junit')(['--tests', _simple_unit_tests()] + args) + return junit(['--tests', _simple_unit_tests()] + args) def junit_noapps(args): - return mx.command_function('junit')(['--tests', _gate_noapps_unit_tests()] + args) + return junit(['--tests', _gate_noapps_unit_tests()] + args) def junit_nopkgs(args): - return mx.command_function('junit')(['--tests', ','.join([_simple_unit_tests(), _nodes_unit_tests()])] + args) + return junit(['--tests', ','.join([_simple_unit_tests(), _nodes_unit_tests()])] + args) def junit_default(args): - return mx.command_function('junit')(['--tests', _all_unit_tests()] + args) + return junit(['--tests', _all_unit_tests()] + args) def junit_gate(args): - return mx.command_function('junit')(['--tests', _gate_unit_tests()] + args) + return junit(['--tests', _gate_unit_tests()] + args) def _test_package(): return 'com.oracle.truffle.r.test' diff --git a/mx.fastr/mx_fastr_junit.py b/mx.fastr/mx_fastr_junit.py new file mode 100644 index 0000000000..71e26428b5 --- /dev/null +++ b/mx.fastr/mx_fastr_junit.py @@ -0,0 +1,113 @@ +# +# Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +from argparse import ArgumentParser, REMAINDER +import os, tempfile +import mx +import mx_fastr + +def junit(args, harness, parser=None, jdk_default=None): + """run Junit tests""" + suppliedParser = parser is not None + parser = parser if suppliedParser else ArgumentParser(prog='mx junit') + parser.add_argument('--tests', action='store', help='pattern to match test classes') + parser.add_argument('--J', dest='vm_args', action='append', help='target VM arguments (e.g. --J @-dsa)', metavar='@<args>') + parser.add_argument('--jdk', action='store', help='jdk to use') + if suppliedParser: + parser.add_argument('remainder', nargs=REMAINDER, metavar='...') + args = parser.parse_args(args) + + vmArgs = ['-ea', '-esa'] + + if args.vm_args: + vmArgs = vmArgs + mx_fastr.split_j_args(args.vm_args) + + testfile = os.environ.get('MX_TESTFILE', None) + if testfile is None: + (_, testfile) = tempfile.mkstemp(".testclasses", "mx") + os.close(_) + + candidates = [] + if args.jdk: + jdk = mx.get_jdk(tag=args.jdk) + if not jdk: + mx.abort("jdk '" + args.jdk + "' not found") + else: + if not jdk_default: + jdk = mx.get_jdk() + else: + jdk = jdk_default + + for p in mx.projects(opt_limit_to_suite=True): + if not p.isJavaProject() or jdk.javaCompliance < p.javaCompliance: + continue + candidates += _find_classes_with_annotations(p, None, ['@Test']).keys() + + tests = [] if args.tests is None else [name for name in args.tests.split(',')] + classes = [] + if len(tests) == 0: + classes = candidates + else: + for t in tests: + found = False + for c in candidates: + if t in c: + found = True + classes.append(c) + if not found: + mx.warn('no tests matched by substring "' + t + '"') + + projectscp = mx.classpath([pcp.name for pcp in mx.projects(opt_limit_to_suite=True) if pcp.isJavaProject() and pcp.javaCompliance <= jdk.javaCompliance], jdk=jdk) + + if len(classes) != 0: + # Compiling wrt projectscp avoids a dependency on junit.jar in mxtool itself + # However, perhaps because it's Friday 13th javac is not actually compiling + # this file, yet not returning error. It is perhaps related to annotation processors + # so the workaround is to extract the junit path as that is all we need. + junitpath = [s for s in projectscp.split(":") if "junit" in s] + if len(junitpath) is 0: + junitpath = [s for s in projectscp.split(":") if "JUNIT" in s] + junitpath = junitpath[0] + + if len(classes) == 1: + testClassArgs = ['--testclass', classes[0]] + else: + with open(testfile, 'w') as f: + for c in classes: + f.write(c + '\n') + testClassArgs = ['--testsfile', testfile] + junitArgs = ['-cp', projectscp, 'com.oracle.truffle.r.test.FastRJUnitWrapper'] + testClassArgs + rc = harness(args, vmArgs, jdk, junitArgs) + return rc + else: + return 0 + +def _find_classes_with_annotations(p, pkgRoot, annotations, includeInnerClasses=False): + """ + Scan the sources of project `p` for Java source files containing a line starting with `annotation` + (ignoring preceding whitespace) and return the fully qualified class name for each Java + source file matched in a list. + """ + + matches = lambda line: len([a for a in annotations if line == a or line.startswith(a + '(')]) != 0 + return p.find_classes_with_matching_source_line(pkgRoot, matches, includeInnerClasses) + -- GitLab