exec_tests.py 13.9 KB
Newer Older
1
import hashlib
2 3 4
import logging
import os
import shutil
5
import signal
6
from os import path
7 8 9 10

import re
from typing import Optional

11 12
from mjtest.environment import TestMode, Environment
from mjtest.test.syntax_tests import BasicSyntaxTest
13
from mjtest.test.tests import TestCase, BasicDiffTestResult, BasicTestResult, ExtensibleTestResult
14
from mjtest.util.shell import SigKill
15
from mjtest.util.utils import get_main_class_name, InsertionTimeOrderedDict
16 17 18 19 20 21 22 23

_LOG = logging.getLogger("exec_tests")

class JavaExecTest(BasicSyntaxTest):
    """
    The MiniJava compiler should behave the same as javac
    """

24
    FILE_ENDINGS = [".java", ".mj"]
25
    INVALID_FILE_ENDINGS = [".inf.java", ".inf.mj", ".input.mj", ".input.java"]
26
    OUTPUT_FILE_ENDING = ".out"
27
    MODE = TestMode.compile_firm
28
    INPUT_FILE_REGEXP = r'(\.[0-9]+)\.input(c?)$'
29 30 31

    def __init__(self, env: Environment, type: str, file: str, preprocessed_file: str):
        super().__init__(env, type, file, preprocessed_file)
32 33 34
        self._has_input_file = bool(re.search(self.INPUT_FILE_REGEXP, file))
        self._input_file = file if self._has_input_file else None
        self._has_character_input = file.endswith("c") if self._has_input_file else False
35
        self._expected_output_file = file + self.OUTPUT_FILE_ENDING
36 37 38 39
        prev_out_dir = path.join(path.dirname(file), ".java_output")
        if not path.exists(prev_out_dir):
            os.mkdir(prev_out_dir)
        self._prev_out_file = path.join(prev_out_dir, path.basename(self._expected_output_file))
40
        self._prev_out_hash_file = self._prev_out_file + "_hash"
41
        self._has_expected_output_file = path.exists(self._expected_output_file)
42
        if not self._has_expected_output_file:
43 44
            if path.exists(self._prev_out_file) and path.exists(self._prev_out_hash_file) \
                    and self._check_hash_sum(self.preprocessed_file, self._prev_out_hash_file):
45 46 47
                self._has_expected_output_file = True
                self._expected_output_file = self._prev_out_file
                _LOG.info("Reuse old java output file \"{}\"".format(path.relpath(self._prev_out_file)))
48 49 50
        self._should_succeed = True

    def run(self) -> BasicDiffTestResult:
51 52
        is_big_testcase = "big" in self.file
        timeout = self.env.big_timeout if is_big_testcase else self.env.timeout
53
        base_filename = path.basename(self.file).split(".")[0]
Johannes Bechberger's avatar
Johannes Bechberger committed
54
        tmp_dir = self.env.create_pid_local_tmpdir()
55 56 57 58
        shutil.copy(self.preprocessed_file, path.join(tmp_dir, base_filename + ".java"))
        cwd = os.getcwd()
        os.chdir(tmp_dir)
        exp_out = None
59
        input_str = None
60
        #print(base_filename, get_main_class_name(base_filename + ".java"))
61 62 63

        test_result = ExtensibleTestResult(self)

64 65 66 67 68 69 70 71 72 73 74 75 76 77
        if self._has_input_file:
            if self._has_character_input:
                with open(self._input_file, "r") as f:
                    input_str = f.read()
            else:
                with open(self._input_file, "r") as f:
                    chars = bytearray('ascii', 'ignore')  # type: bytearray
                    for line in f.readlines():
                        for part in line.split(" "):
                            part = part.strip()
                            if len(part) > 0:
                                chars.append(int(part))
                input_str = chars.decode()

78
        if not self._has_expected_output_file:
79
            _, err, javac_rtcode = \
80
                self.env.run_command("javac", base_filename + ".java", timeout=timeout)
81 82
            if javac_rtcode != 0:
                _LOG.error("File \"{}\" isn't valid Java".format(self.preprocessed_file))
83
                test_result.incorrect_msg = "invalid java code, but output file missing"
84
                test_result.set_error_code(javac_rtcode)
85 86
                test_result.add_long_text("Javac error message", err.decode())
                test_result.add_file("Source file", self.preprocessed_file)
87
                os.chdir(cwd)
88
                return test_result
89
            main_class = get_main_class_name(base_filename + ".java")
90 91 92
            if not main_class:
                _LOG.debug("Can't find a main class, using the file name instead")
                main_class = base_filename
93
            exp_out, err, java_rtcode = \
94
                self.env.run_command("java", get_main_class_name(base_filename + ".java"), timeout=timeout, input_str=input_str)
95
            if javac_rtcode != 0:
96
                test_result.add_long_text("Java output: ", exp_out.decode())
97
                test_result.incorrect_msg = "java runtime error"
98
                test_result.set_error_code(java_rtcode)
99 100
                test_result.add_long_text("Java error message", err.decode())
                test_result.add_file("Source file", self.preprocessed_file)
101 102
                if self._input_file:
                    test_result.add_file("Input file", self.file)
103 104
                os.chdir(cwd)
                return test_result
105
            exp_out = exp_out.decode()
106

107 108 109
            with open(self._prev_out_file, "w") as f:
                f.write(exp_out)
                f.flush()
110 111 112
            with open(self._prev_out_hash_file, "w") as f:
                f.write(self._hash_sum_for_file(base_filename + ".java"))
                f.flush()
113 114 115
        if self._has_expected_output_file and self.type == self.MODE and self.env.mode == self.MODE:
            with open(self._expected_output_file, "r") as f:
                exp_out = f.read()
116

117
        try:
118 119
            out, err, rtcode = None, None, None
            try:
120
                out, err, rtcode = self.env.run_mj_command(self.MODE, base_filename + ".java", timeout=timeout)
121 122
                if rtcode != 0:
                    test_result.incorrect_msg = "file can't be compiled"
123
                    test_result.set_error_code(rtcode)
124 125 126 127 128 129 130
                    test_result.add_long_text("Error output", err.decode())
                    test_result.add_long_text("Output", out.decode())
                    test_result.add_file("Source file", self.preprocessed_file)
                    os.chdir(cwd)
                    return test_result
            except SigKill as sig:
                test_result.incorrect_msg = "file can't be compiled: " + sig.name
131
                test_result.set_error_code(sig.retcode)
132 133 134 135 136 137 138
                test_result.add_file("Source file", self.preprocessed_file)
                os.chdir(cwd)
                return test_result
            except:
                os.chdir(cwd)
                raise
            try:
139
                out, err, rtcode = self.env.run_command("./a.out", timeout=timeout, input_str=input_str)
140 141 142 143 144 145
                if rtcode != 0:
                    test_result.incorrect_msg = "binary can't be run, non zero error code"
                    test_result.set_error_code(rtcode)
                    test_result.add_long_text("Error output", err.decode())
                    test_result.add_long_text("Output", out.decode())
                    test_result.add_file("Source file", self.preprocessed_file)
146 147
                    if self._input_file:
                        test_result.add_file("Input file", self.file)
148 149
                    os.chdir(cwd)
                    return test_result
150
            except SigKill as sig:
151 152
                test_result.incorrect_msg = "binary can't be run: " + sig.name.strip()
                test_result.set_error_code(sig.retcode)
153
                test_result.add_file("Source file", self.preprocessed_file)
154 155
                if self._input_file:
                    test_result.add_file("Input file", self.file)
156 157 158 159 160
                os.chdir(cwd)
                return test_result
            except:
                os.chdir(cwd)
                raise
161
            out = out.decode()
162
            if self.type == self.MODE and self.env.mode == self.MODE:
163
                test_result.add_long_text("Output", out)
164 165
                if exp_out.strip() != out.strip():
                    test_result.incorrect_msg = "incorrect output"
166
                    test_result.has_succeeded = False
167 168 169
                    test_result.add_short_text("Expected output file", self._expected_output_file)
                    test_result.add_long_text("Expected output", exp_out)
                    test_result.add_long_text("Actual output", out)
170
                    test_result.add_diff("Output diff [expected <-> actual]", exp_out, out, with_line_numbers=True)
171
                    test_result.add_file("Source file", self.preprocessed_file)
172 173
                    if self._input_file:
                        test_result.add_file("Input file", self.file)
174 175
                os.chdir(cwd)
                return test_result
Johannes Bechberger's avatar
Johannes Bechberger committed
176
            return BasicTestResult(self, rtcode, out, err.decode())
177 178
        except SigKill as sig:
            os.chdir(cwd)
179
            assert False
180 181
        except:
            os.chdir(cwd)
182
            assert False
183

184 185 186
    def _check_hash_sum(self, file: str, hash_sum_file: str) -> bool:
        old_hash = ""
        with open(hash_sum_file, "r") as f:
187 188 189 190
            try:
                old_hash = f.readline().strip()
            except UnicodeDecodeError:
                _LOG.exception("Decoding a hash sum for java output caching failed")
191
        return self._hash_sum_for_file(file) == old_hash
192

193
    @classmethod
194 195 196
    def _hash_sum_for_file(self, file: str) -> str:
        with open(file, "r") as f:
            return hashlib.sha256(f.read().encode()).hexdigest()
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    @classmethod
    def _test_case_file_for_input_file(cls, input_file: str) -> Optional[str]:
        base = re.sub(cls.INPUT_FILE_REGEXP, "", input_file)
        if path.exists(base + ".input.mj"):
            return base + ".input.mj"
        if path.exists(base + ".input.java"):
            return base + ".input.java"
        return None

    @staticmethod
    def is_file_ending_valid(cls, file: str) -> bool:
        if re.search(cls.INPUT_FILE_REGEXP, file) and ".java_output" not in file and "precprocessor" not in file:
            if not JavaExecTest._test_case_file_for_input_file(file):
                _LOG.error("Skip {} that hasn't a corresponding test case file".format(file))
            return True
        return super().is_file_ending_valid(cls, file)

    @staticmethod
    def _get_test_case_file(cls, file: str) -> Optional[str]:
        if re.search(JavaExecTest.INPUT_FILE_REGEXP, file):
            return JavaExecTest._test_case_file_for_input_file(file)
        return file

221 222 223 224 225
TestCase.TEST_CASE_CLASSES[TestMode.compile_firm].append(JavaExecTest)


class JavaInfiniteLoopTest(BasicSyntaxTest):

226 227
    FILE_ENDINGS = [".inf.java", ".inf.mj"]
    OUTPUT_FILE_ENDING = ".out"
228 229 230 231
    MODE = TestMode.compile_firm

    def __init__(self, env: Environment, type: str, file: str, preprocessed_file: str):
        super().__init__(env, type, file, preprocessed_file)
232 233
        self._output_file = self.file + self.OUTPUT_FILE_ENDING
        self._has_output_file = path.exists(self._output_file)
234 235 236 237 238 239 240 241

    def run(self) -> BasicTestResult:
        base_filename = path.basename(self.file).split(".")[0]
        tmp_dir = self.env.create_pid_local_tmpdir()
        shutil.copy(self.preprocessed_file, path.join(tmp_dir, base_filename + ".java"))
        cwd = os.getcwd()
        os.chdir(tmp_dir)
        timeout = 1
242 243 244 245
        err = None
        out = None
        rtcode = None
        test_result = ExtensibleTestResult(self)
246
        try:
247 248 249
            out, err, rtcode = self.env.run_mj_command(self.MODE, base_filename + ".java")
            if rtcode != 0:
                test_result.incorrect_msg = "file can't be compiled"
250
                test_result.set_error_code(rtcode)
251 252 253 254 255
                test_result.add_long_text("Error output", err.decode())
                test_result.add_long_text("Output", out.decode())
                test_result.add_file("Source file", self.preprocessed_file)
                os.chdir(cwd)
                return test_result
Johannes Bechberger's avatar
Johannes Bechberger committed
256
            out, err, rtcode = self.env.run_command("./a.out", timeout=timeout)
257 258 259 260 261 262 263 264
            if rtcode != 0:
                test_result.incorrect_msg = "binary can't be run, non zero error code"
                test_result.set_error_code(rtcode)
                test_result.add_long_text("Error output", err.decode())
                test_result.add_long_text("Output", out.decode())
                test_result.add_file("Source file", self.preprocessed_file)
                os.chdir(cwd)
                return test_result
265 266
        except SigKill as sig:
            if sig.retcode == signal.SIGXCPU:
267
                out, _, _, _ = self.env.run_command_till_timeout("./a.out", timeout=1)
268
                test_result.add_long_text("Output", out.decode())
269 270 271 272 273
                if self._has_output_file:
                    out = out.decode().strip()
                    exp_out = ""
                    with open(self._output_file, "r") as f:
                        exp_out = f.read().strip()
274
                    test_result.add_long_text("Expected output start", exp_out)
275
                    if not out.startswith(exp_out):
276 277 278 279 280 281 282 283 284
                        test_result.incorrect_msg = "incorrect output start"
                        test_result.has_succeeded = False
                    test_result.add_file("Source file", self.preprocessed_file)
                    os.chdir(cwd)
                    return test_result
                else:
                    test_result.add_file("Source file", self.preprocessed_file)
                    os.chdir(cwd)
                    return test_result
285
            else:
286
                test_result.incorrect_msg = "binary can't be run: " + sig.name
287
                test_result.set_error_code(sig.retcode)
288 289 290
                test_result.add_file("Source file", self.preprocessed_file)
                os.chdir(cwd)
                return test_result
291 292 293 294
        except:
            os.chdir(cwd)
            raise
        os.chdir(cwd)
295 296 297 298 299 300
        test_result.incorrect_msg = "run shorter than one second"
        test_result.add_long_text("Output", out)
        test_result.has_succeeded = False
        test_result.add_file("Source file", self.preprocessed_file)
        os.chdir(cwd)
        return test_result
301 302


303
TestCase.TEST_CASE_CLASSES[TestMode.compile_firm].append(JavaInfiniteLoopTest)