environment.py 6.16 KB
Newer Older
Johannes Bechberger's avatar
Johannes Bechberger committed
1
2
import logging
import os
Johannes Bechberger's avatar
Johannes Bechberger committed
3
import random
Johannes Bechberger's avatar
Johannes Bechberger committed
4
5
import shutil
import tempfile
Johannes Bechberger's avatar
Johannes Bechberger committed
6
7
from datetime import datetime
import time
Johannes Bechberger's avatar
Johannes Bechberger committed
8
from mjtest.util.shell import execute
9
10
from mjtest.util.utils import get_mjtest_basedir, disable_colored_output
import mjtest.util.utils
Johannes Bechberger's avatar
Johannes Bechberger committed
11
from typing import Tuple, List
12
from preproc.preproc.preprocessor import PreProcessor, PreProcessorError, is_importable_file
Johannes Bechberger's avatar
Johannes Bechberger committed
13

14
_LOG = logging.getLogger("env")
Johannes Bechberger's avatar
Johannes Bechberger committed
15
16

class TestMode:
17
18
19

    lexer = "lexer"

Johannes Bechberger's avatar
Johannes Bechberger committed
20
21
    syntax = "syntax"

Johannes Bechberger's avatar
Johannes Bechberger committed
22
23
    ast = "ast"

Johannes Bechberger's avatar
Johannes Bechberger committed
24
25
26
27
    semantic = "semantic"

    exec = "exec"

Johannes Bechberger's avatar
Johannes Bechberger committed
28
29
30
31
    USE_TESTS_OF_OTHER = {
        ast: [syntax]
    }

Johannes Bechberger's avatar
Johannes Bechberger committed
32
""" All 'success' tests of the n.th mode can used as 'success' tests for the n-1.th mode"""
33
TEST_MODES = [TestMode.lexer, TestMode.syntax, TestMode.ast, TestMode.semantic, TestMode.exec]
Johannes Bechberger's avatar
Johannes Bechberger committed
34
35
36
37
38
39
40
41
42
43

class Environment:

    LOG_LEVELS = {
        "info": logging.INFO,
        "error": logging.ERROR,
        "warn": logging.WARN,
        "debug": logging.DEBUG
    }

44
    def __init__(self, mode, mj_run: str, tmp_dir: str = "", test_dir: str = "",
Johannes Bechberger's avatar
Johannes Bechberger committed
45
                 only_incorrect_tests: bool = False, parallel: bool = False,
46
                 timeout: int = 30, report_dir: str = "", log_level: str = "warn",
47
                 produce_no_reports: bool = True, output_no_incorrect_reports: bool = False,
48
                 produce_all_reports: bool = False, report_subdir: str = None,
49
50
51
                 ci_testing: bool = False, no_color: bool = False):
        if no_color:
            disable_colored_output()
Johannes Bechberger's avatar
Johannes Bechberger committed
52
        self.mode = mode
53
        self.mj_run_cmd = os.path.realpath(mj_run)
Johannes Bechberger's avatar
Johannes Bechberger committed
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

        if tmp_dir:
            self.own_tmp_dir = True
            self.tmp_dir = os.path.abspath(os.path.expandvars(tmp_dir))
            if not os.path.exists(tmp_dir):
                os.mkdir(self.tmp_dir)
        else:
            self.own_tmp_dir = False
            self.tmp_dir = tempfile.mkdtemp("mjtest")

        if test_dir:
            self.test_dir = os.path.abspath(os.path.realpath(test_dir))
        else:
            self.test_dir = os.path.join(get_mjtest_basedir(), "tests")
        if not os.path.exists(self.test_dir):
            os.mkdir(self.test_dir)
Johannes Bechberger's avatar
Johannes Bechberger committed
70
            for d in TEST_MODES:
Johannes Bechberger's avatar
Johannes Bechberger committed
71
72
73
74
75
                os.mkdir(os.path.join(self.test_dir, d))

        self.only_incorrect_tests = only_incorrect_tests
        self.parallel = parallel
        self.timeout = timeout
76
77
78
79
80
        if not produce_no_reports:
            if report_dir:
                self.report_dir = os.path.abspath(os.path.expandvars(report_dir))
            else:
                self.report_dir = os.path.join(get_mjtest_basedir(), "reports")
81
82
83
84
            try:
                os.mkdir(self.report_dir)
            except IOError:
                pass
85
            self.report_dir = os.path.join(self.report_dir, report_subdir or datetime.now().strftime("%d-%m-%y_%H-%M-%S"))
Johannes Bechberger's avatar
Johannes Bechberger committed
86
        else:
87
            self.report_dir = None
Johannes Bechberger's avatar
Johannes Bechberger committed
88
        logging.basicConfig(level=self.LOG_LEVELS[log_level])
89
        self.produce_reports = not produce_no_reports # type: bool
90
91
        self.output_incorrect_reports = not output_no_incorrect_reports
        self.produce_all_reports = produce_all_reports
92
        self.ci_testing = ci_testing
Johannes Bechberger's avatar
Johannes Bechberger committed
93
        self._tmp_file_ctr = 0
94
        self._already_preprocessed_files = set()
Johannes Bechberger's avatar
Johannes Bechberger committed
95
96

    def create_tmpfile(self) -> str:
Johannes Bechberger's avatar
Johannes Bechberger committed
97
98
99
        self._tmp_file_ctr += 1
        return os.path.join(self.tmp_dir, str(round(time.time() * 100000))
                            + str(random.randrange(0, 10000, 1)) + str(self._tmp_file_ctr))
100
101
102
103
104

    def create_tmpdir(self) -> str:
        dir = self.create_tmpfile()
        os.mkdir(dir)
        return dir
Johannes Bechberger's avatar
Johannes Bechberger committed
105
106
107
108
109

    def clean_up(self):
        if not self.own_tmp_dir:
            shutil.rmtree(self.tmp_dir)

Johannes Bechberger's avatar
Johannes Bechberger committed
110
    def run_mj_command(self, mode: str, *args: Tuple[str]) -> Tuple[bytes, bytes, int]:
Johannes Bechberger's avatar
Johannes Bechberger committed
111
112
113
114
115
116
        """
        Execute the MiniJava `run` script with the given arguments.

        :param args: arguments for the MiniJava `run` script
        :return: (out, err, return code)
        """
Johannes Bechberger's avatar
Johannes Bechberger committed
117
        mode_flag = {
118
            TestMode.lexer: "--lextest",
Johannes Bechberger's avatar
Johannes Bechberger committed
119
            TestMode.syntax: "--parsetest",
Johannes Bechberger's avatar
Johannes Bechberger committed
120
121
            TestMode.ast: "--print-ast",
            TestMode.semantic: "--check"
Johannes Bechberger's avatar
Johannes Bechberger committed
122
123
        }[mode]
        cmd = [self.mj_run_cmd, mode_flag] + list(args)
124
125
126
127
128
129
130
131
        return execute(cmd, timeout=self.timeout)

    def run_command(self, cmd: str, *args: Tuple[str]) -> Tuple[bytes, bytes, int]:
        """
        Execute the passend command with its arguments

        :return: (out, err, return code)
        """
132
        return execute([cmd] + list(args), timeout=self.timeout)
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

    def has_to_preprocess(self, file: str) -> bool:
        return os.path.relpath(file, self.test_dir).startswith("exec")

    def is_lib_file(self, file: str) -> bool:
        return is_importable_file(file)

    def preprocess(self, file: str) -> str:
        """
        Pre process the passed file if needed and return the resulting file
        """
        if not self.has_to_preprocess(file):
            return file
        if ".preprocessed" in os.path.relpath(file, self.test_dir):
            return file

        import_base_dir = os.path.join(self.test_dir, os.path.relpath(file, self.test_dir).lstrip(os.sep).split(os.sep)[0])
        dst_dir = os.path.join(import_base_dir, ".preprocessed", os.path.split(os.path.relpath(file, import_base_dir))[0])
        dst_file = os.path.join(dst_dir, os.path.basename(file))
        if dst_file.endswith(".mj"):
            dst_file = dst_file.replace(".mj", ".java")
        if dst_file in self._already_preprocessed_files:
            return dst_file
        self._already_preprocessed_files.add(dst_file)

        if os.path.exists(dst_file) and os.path.isfile(dst_file) and os.path.getmtime(file) < os.path.getmtime(dst_file):
            _LOG.debug("File '{}' already exists in a pre processed form".format(os.path.relpath(file)))
            return dst_file
        cur = os.path.split(dst_file)[0]
        while not os.path.exists(cur):
            os.mkdir(cur)
            cur = os.path.split(cur)[0]

        if not os.path.exists(dst_dir):
            os.mkdir(dst_dir)
        try:
            PreProcessor(file, import_base_dir, dst_file).preprocess()
        except:
            _LOG.exception("Pre processing file '{}'".format(file))
            raise
        return dst_file