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