Skip to content

Commit 2b441f6

Browse files
Merge pull request #270 from MarkUsProject/v1.10.2-rc
V1.10.2 rc
2 parents ba8c972 + 9e5476e commit 2b441f6

File tree

16 files changed

+139
-496
lines changed

16 files changed

+139
-496
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ src/autotester/testers/*/specs/.installed
2626
src/autotester/testers/*/specs/install_settings.json
2727

2828
# java
29-
src/autotester/testers/java/lib/.gradle
30-
src/autotester/testers/java/lib/build
29+
src/autotester/testers/java/lib/*.jar
3130

3231
# racket
3332
src/autotester/testers/racket/**/compiled/

Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# CHANGELOG
22
All notable changes to this project will be documented here.
33

4+
## [v1.10.2]
5+
- Updated java tester to support configurable classpaths and source files (#268)
6+
47
## [v1.10.1]
58
- Fixed bug where relevant test data was not included in the client while enqueuing tests (#265)
69

@@ -9,6 +12,7 @@ All notable changes to this project will be documented here.
912
- Removed Tasty-Stats as a dependency for the haskell tester and added our own ingredient instead to collect stats (#259)
1013
- Updated/improved the interface between the autotester and clients that use it (#257)
1114

15+
1216
## [1.9.0]
1317
- allow tests to write to existing subdirectories but not overwrite existing test script files (#237).
1418
- add ability to create a docker container for the autotester in development mode (#236).

src/autotester/testers/java/bin/install.sh

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,9 @@ install_packages() {
1515
sudo DEBIAN_FRONTEND=${debian_frontend} apt-get ${apt_yes} "${apt_opts[@]}" install openjdk-8-jdk
1616
}
1717

18-
compile_tester() {
19-
echo "[JAVA-INSTALL] Compiling tester"
20-
pushd "${JAVADIR}" > /dev/null
21-
./gradlew installDist --no-daemon
22-
popd > /dev/null
23-
}
24-
25-
update_specs() {
26-
echo "[JAVA-INSTALL] Updating specs"
27-
echo '{}' | jq ".path_to_tester_jars = \"${JAVADIR}/build/install/JavaTester/lib\"" > "${SPECSDIR}/install_settings.json"
18+
install_requirements() {
19+
echo "[JAVA-INSTALL] Installing requirements"
20+
wget https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.7.0/junit-platform-console-standalone-1.7.0.jar -O "${JAVADIR}/junit-platform-console-standalone.jar"
2821
}
2922

3023
# script starts here
@@ -42,6 +35,5 @@ NON_INTERACTIVE=$1
4235

4336
# main
4437
install_packages
45-
compile_tester
46-
update_specs
38+
install_requirements
4739
touch "${SPECSDIR}/.installed"

src/autotester/testers/java/bin/uninstall.sh

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@
22

33
remove_tester() {
44
echo "[JAVA-UNINSTALL] Removing compiled tester"
5-
rm -rf "${JAVADIR}/build"
6-
rm -rf "${JAVADIR}/.gradle"
7-
}
8-
9-
reset_specs() {
10-
echo "[JAVA-UNINSTALL] Resetting specs"
11-
rm -f "${SPECSDIR}/install_settings.json"
5+
rm -r "${JAVADIR:?}/junit-platform-console-standalone.jar"
126
}
137

148
# script starts here
@@ -26,5 +20,5 @@ JAVADIR=$(readlink -f "${THISDIR}/../lib")
2620
# main
2721
remove_tester
2822
reset_specs
29-
echo "[JAVA-UNINSTALL] The following system packages have not been uninstalled: python3 openjdk-12-jdk jq. You may uninstall them if you wish."
23+
echo "[JAVA-UNINSTALL] The following system packages have not been uninstalled: python3 openjdk-8-jdk jq. You may uninstall them if you wish."
3024
rm -f "${SPECSDIR}/.installed"
Lines changed: 86 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,39 @@
1-
import enum
2-
import json
1+
import os
32
import subprocess
4-
from typing import Dict, Optional, IO, Type
5-
3+
import tempfile
4+
import xml.etree.ElementTree as eTree
5+
from glob import glob
6+
from typing import Type, List, Set
67
from testers.specs import TestSpecs
78
from testers.tester import Tester, Test, TestError
89

910

1011
class JavaTest(Test):
11-
class JUnitStatus(enum.Enum):
12-
SUCCESSFUL = 1
13-
ABORTED = 2
14-
FAILED = 3
15-
16-
ERRORS = {
17-
"bad_javac": 'Java compilation error: "{}"',
18-
"bad_java": 'Java runtime error: "{}"',
19-
}
20-
21-
def __init__(self, tester: "JavaTester", result: Dict, feedback_open: Optional[IO] = None,) -> None:
22-
"""
23-
Initialize a Java test created by tester.
24-
25-
The result was created after running some junit tests.
26-
Test feedback will be written to feedback_open.
27-
"""
28-
self.class_name, _sep, self.method_name = result["name"].partition(".")
29-
self.description = result.get("description")
30-
self.status = JavaTest.JUnitStatus[result["status"]]
31-
self.message = result.get("message")
12+
def __init__(self, tester, result, feedback_open=None):
13+
self._test_name = result['name']
14+
self.status = result['status']
15+
self.message = result['message']
3216
super().__init__(tester, feedback_open)
3317

3418
@property
35-
def test_name(self) -> str:
36-
""" The name of this test """
37-
name = f"{self.class_name}.{self.method_name}"
38-
if self.description:
39-
name += f" ({self.description})"
40-
return name
19+
def test_name(self):
20+
return self._test_name
4121

4222
@Test.run_decorator
43-
def run(self) -> str:
44-
"""
45-
Return a json string containing all test result information.
46-
"""
47-
if self.status == JavaTest.JUnitStatus.SUCCESSFUL:
48-
return self.passed()
49-
elif self.status == JavaTest.JUnitStatus.FAILED:
23+
def run(self):
24+
if self.status == "success":
25+
return self.passed(message=self.message)
26+
elif self.status == "failure":
5027
return self.failed(message=self.message)
5128
else:
5229
return self.error(message=self.message)
5330

5431

5532
class JavaTester(Tester):
5633

57-
JAVA_TESTER_CLASS = "edu.toronto.cs.teach.JavaTester"
34+
JUNIT_TESTER_JAR = os.path.join(os.path.dirname(__file__), "lib", "junit-platform-console-standalone.jar")
35+
JUNIT_JUPITER_RESULT = "TEST-junit-jupiter.xml"
36+
JUNIT_VINTAGE_RESULT = "TEST-junit-vintage.xml"
5837

5938
def __init__(self, specs: TestSpecs, test_class: Type[JavaTest] = JavaTest) -> None:
6039
"""
@@ -63,17 +42,62 @@ def __init__(self, specs: TestSpecs, test_class: Type[JavaTest] = JavaTest) -> N
6342
This tester will create tests of type test_class.
6443
"""
6544
super().__init__(specs, test_class)
66-
self.java_classpath = f'.:{self.specs["install_data", "path_to_tester_jars"]}/*'
45+
classpath = self.specs.get("test_data", "classpath", default='.') or '.'
46+
self.java_classpath = ":".join(self._parse_file_paths(classpath))
47+
self.out_dir = tempfile.TemporaryDirectory(dir=os.getcwd())
48+
self.reports_dir = tempfile.TemporaryDirectory(dir=os.getcwd())
6749

68-
def compile(self) -> None:
50+
@staticmethod
51+
def _parse_file_paths(glob_string: str) -> List[str]:
52+
"""
53+
Return the real (absolute) paths of all files described by the glob <glob_string>.
54+
Only files that exist in the current directory (or its subdirectories) are returned.
55+
"""
56+
curr_path = os.path.realpath('.')
57+
return [x for p in glob_string.split(':') for x in glob(os.path.realpath(p)) if curr_path in x]
58+
59+
def _get_sources(self) -> Set:
60+
"""
61+
Return all java source files for this test.
62+
"""
63+
sources = self.specs.get("test_data", "sources_path", default='')
64+
scripts = ':'.join(self.specs["test_data", "script_files"] + [sources])
65+
return {path for path in self._parse_file_paths(scripts) if os.path.splitext(path)[1] == '.java'}
66+
67+
def _parse_junitxml(self):
68+
"""
69+
Parse junit results and yield a hash containing result data for each testcase.
70+
"""
71+
for xml_filename in [self.JUNIT_JUPITER_RESULT, self.JUNIT_VINTAGE_RESULT]:
72+
tree = eTree.parse(os.path.join(self.reports_dir.name, xml_filename))
73+
root = tree.getroot()
74+
for testcase in root.iterfind('testcase'):
75+
result = {}
76+
classname = testcase.attrib['classname']
77+
testname = testcase.attrib['name']
78+
result['name'] = '{}.{}'.format(classname, testname)
79+
result['time'] = float(testcase.attrib.get('time', 0))
80+
failure = testcase.find('failure')
81+
if failure is not None:
82+
result['status'] = 'failure'
83+
failure_type = failure.attrib.get('type', '')
84+
failure_message = failure.attrib.get('message', '')
85+
result['message'] = f'{failure_type}: {failure_message}'
86+
else:
87+
result['status'] = 'success'
88+
result['message'] = ''
89+
yield result
90+
91+
def compile(self) -> subprocess.CompletedProcess:
6992
"""
7093
Compile the junit tests specified in the self.specs specifications.
7194
"""
72-
javac_command = ["javac", "-cp", self.java_classpath]
73-
javac_command.extend(self.specs["test_data", "script_files"])
95+
classpath = f"{self.java_classpath}:{self.JUNIT_TESTER_JAR}"
96+
javac_command = ["javac", "-cp", classpath, "-d", self.out_dir.name]
97+
javac_command.extend(self._get_sources())
7498
# student files imported by tests will be compiled on cascade
75-
subprocess.run(
76-
javac_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, check=True,
99+
return subprocess.run(
100+
javac_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, check=False,
77101
)
78102

79103
def run_junit(self) -> subprocess.CompletedProcess:
@@ -82,13 +106,19 @@ def run_junit(self) -> subprocess.CompletedProcess:
82106
"""
83107
java_command = [
84108
"java",
85-
"-cp",
86-
self.java_classpath,
87-
JavaTester.JAVA_TESTER_CLASS,
109+
"-jar",
110+
self.JUNIT_TESTER_JAR,
111+
f"-cp={self.java_classpath}:{self.out_dir.name}",
112+
f"--reports-dir={self.reports_dir.name}"
88113
]
89-
java_command.extend(self.specs["test_data", "script_files"])
114+
classes = [f"-c={os.path.splitext(os.path.basename(f))[0]}" for f in self.specs["test_data", "script_files"]]
115+
java_command.extend(classes)
90116
java = subprocess.run(
91-
java_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, check=True,
117+
java_command,
118+
stdout=subprocess.PIPE,
119+
stderr=subprocess.PIPE,
120+
universal_newlines=True,
121+
check=False
92122
)
93123
return java
94124

@@ -99,20 +129,20 @@ def run(self) -> None:
99129
"""
100130
# check that the submission compiles against the tests
101131
try:
102-
self.compile()
132+
compile_result = self.compile()
133+
if compile_result.stderr:
134+
raise TestError(compile_result.stderr)
103135
except subprocess.CalledProcessError as e:
104-
msg = JavaTest.ERRORS["bad_javac"].format(e.stdout)
105-
raise TestError(msg) from e
136+
raise TestError(e)
106137
# run the tests with junit
107138
try:
108139
results = self.run_junit()
109140
if results.stderr:
110141
raise TestError(results.stderr)
111142
except subprocess.CalledProcessError as e:
112-
msg = JavaTest.ERRORS["bad_java"].format(e.stdout + e.stderr)
113-
raise TestError(msg) from e
143+
raise TestError(e)
114144
with self.open_feedback() as feedback_open:
115-
for result in json.loads(results.stdout):
145+
for result in self._parse_junitxml():
116146
test = self.test_class(self, result, feedback_open)
117147
result_json = test.run()
118148
print(result_json, flush=True)

src/autotester/testers/java/lib/.gitkeep

Whitespace-only changes.

src/autotester/testers/java/lib/build.gradle

Lines changed: 0 additions & 20 deletions
This file was deleted.
-54.3 KB
Binary file not shown.

src/autotester/testers/java/lib/gradle/wrapper/gradle-wrapper.properties

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)