Пример #1
0
def runTest(files, fun):
    with shell.tempDir() as d:
        c = mkConfig(d)
        studDir = shell.pjoin(d, student)
        shell.mkdir(studDir)
        for f in files:
            shell.touch(shell.pjoin(studDir, f))
        fun(c)
Пример #2
0
def copyFileIfNotExists(srcDir, path, targetDir):
    srcPath = shell.pjoin(srcDir, path)
    if not shell.isFile(srcPath):
        raise IOError(f'{srcPath} must be a file')
    tgtPath = shell.pjoin(targetDir, path)
    if shell.isDir(tgtPath):
        raise IOError(f'{tgtPath} must not be a directory')
    shell.mkdir(shell.dirname(tgtPath), createParents=True)
    if shell.isFile(tgtPath):
        if not hasSameContent(srcPath, tgtPath):
            raise IOError(f'Target file {tgtPath} already exists with content different than in {srcPath}')
    else:
        shell.cp(srcPath, tgtPath)
Пример #3
0
def copyIntoStudentDir(assignment: Assignment, studentDir: str):
    for src in assignment.itemsToCopy:
        target = shell.pjoin(studentDir, shell.basename(src))
        if not fileSystemItemEquals(src, target):
            print(f'Copying {src} to {studentDir} ...')
            moveToBackup(target)
            shell.cp(src, studentDir)
Пример #4
0
 def parse(baseDir, configDict, ymlDict):
     assignments= []
     for k, v in ymlDict['assignments'].items():
         a = Assignment.parse(k, [v, ymlDict])
         assignments.append(a)
     testDir = ymlDict.get('test-dir', shell.pjoin(baseDir, 'tests'))
     return Config(baseDir, configDict, assignments, testDir)
Пример #5
0
def runJplag(opts, args, fileGlob=None):
    jarName = 'jplag-3.0.0-jar-with-dependencies.jar'
    jarFile = shell.pjoin(shell.dirname(__file__), '..', 'resources', jarName)
    print('Running jplag, this might take a while ...')
    res = shell.run(['java', '-jar', jarFile] + opts,
                    captureStdout=True,
                    stderrToStdout=True)
    haveMinScore = False
    for l in res.stdout.split('\n'):
        if 'no viable alternative at input' in l:
            pass
        elif 'nothing to parse for submission' in l:
            pass
        elif l.startswith('Comparing '):
            i = -1
            try:
                i = l.rindex(':')
            except ValueError:
                verbose(l)
            if i >= 0:
                score = float(l[i + 1:].strip())
                if score >= args.minScore:
                    haveMinScore = True
                    print(ansi.red(l))
                    if args.printDiff and score >= 99.99999999999999999:
                        printDiffForJplag(l, fileGlob)
        elif 'Writing results to' in l:
            print(ansi.blue(l + "/index.html"))
        else:
            verbose(l)
    if not haveMinScore:
        print(f'No submissions detected with similaries >= {args.minScore}')
Пример #6
0
def runJplagForFile(lang, kind, file, args):
    print()
    print(ansi.green(f'Running jplag for file {file}'))
    runJplag([
        '-r',
        shell.pjoin(resultDir, kind, file), '-l', lang, '-p', file, '.'
    ], args, file)
Пример #7
0
 def parse(baseDir, id, dicts):
     points = getFromDicts(dicts, Keys.points, int)
     kind = getFromDicts(dicts, Keys.kind)
     tests = getFromDicts(dicts, Keys.tests, default={}, fail=False)
     if not tests:
         files = getSingularPlural(dicts, Keys.testFile, Keys.testFiles)
         filters = getSingularPlural(dicts, Keys.testFilter,
                                     Keys.testFilters)
         dir = getFromDicts(dicts,
                            Keys.testDir,
                            default=defaultTestDir(baseDir))
         parsedTests = []
         for f in files:
             testId = str(id)
             if len(files) + len(filters) > 1:
                 testId = f'{testId}_{shell.removeExt(shell.basename(f))}'
             parsedTests.append(Test(testId, dir, None, shell.pjoin(dir,
                                                                    f)))
         for f in filters:
             testId = str(id)
             if len(files) + len(filters) > 1:
                 testId = f'{testId}_{f.replace("*", "ALL")}'
             parsedTests.append(Test(testId, dir, f, None))
     else:
         parsedTests = []
         for k, v in tests.items():
             t = Test.parse(baseDir, id, k, [v] + dicts)
             parsedTests.append(t)
     disabledTests = getFromDicts(dicts, 'disable-tests', default=False)
     if disabledTests:
         parsedTests = []
     return Assignment(id, baseDir, points, kind, parsedTests, dicts)
Пример #8
0
def mkConfig(baseDir, configDict):
    if not shell.isdir(baseDir):
        abort('Base directory {baseDir} does not exist')
    yamlPath = shell.pjoin(baseDir, 'check.yml')
    if not shell.isFile(yamlPath):
        abort(f'Config file {yamlPath} not found')
    s = utils.readFile(yamlPath)
    return mkConfigFromString(baseDir, configDict, s)
Пример #9
0
def mkConfig(baseDir, configDict):
    if not shell.isdir(baseDir):
        abort('Base directory {baseDir} does not exist')
    yamlPath = shell.pjoin(baseDir, 'check.yml')
    if not shell.isFile(yamlPath):
        abort(f'Config file {yamlPath} not found')
    s = utils.readFile(yamlPath)
    ymlDict = yaml.load(s, Loader=yaml.FullLoader)
    return Config.parse(baseDir, configDict, ymlDict)
Пример #10
0
 def getFile(self, k, d, fail=True):
     x = getFromDicts(self.dicts, k, fail=fail)
     if x is not None:
         return shell.pjoin(d, x)
     else:
         if fail:
             raise ValueError(f'Key {k} must be set for assignment {self.id}')
         else:
             return None
Пример #11
0
 def action(student, k, d, missing, superfluous):
     for m in missing:
         candidates = []
         for s in superfluous:
             if s.endswith(m) or len(superfluous) == 1:
                 candidates.append(s)
         verbose(
             f'Fixing filenames for student {student} and kind {k}. candidates={candidates}'
         )
         if len(candidates) > 1:
             print(
                 f'Cannot fix name of assignment {m} for {student} because there is more than one matching file'
             )
         elif len(candidates) == 1:
             c = candidates[0]
             src = shell.pjoin(d, c)
             tgt = shell.pjoin(d, m)
             verbose(f'Renaming {src} to {tgt}')
             shell.mv(src, tgt)
Пример #12
0
def moveToBackup(path):
    if not shell.exists(path):
        return
    for i in range(1000):
        backupName = shell.pjoin(shell.dirname(path),
                                 '.' + shell.basename(path) + ".bak")
        if i > 0:
            backupName = backupName + "." + str(i)
        if not shell.exists(backupName):
            shell.mv(path, backupName)
            return
    raise ValueError(f"too many backups for {path}")
Пример #13
0
def fixFilenames(config):
    submissionDirs = collectSubmissionDirs(config)
    for d in submissionDirs:
        student = shell.basename(d)
        files = set([
            shell.basename(f) for f in shell.ls(d, config.submissionFileGlob)
        ])
        expected = set(config.assignments)
        missing = expected - files
        superfluous = files - expected
        for m in missing:
            candidates = []
            for s in superfluous:
                if s.endswith(m) or len(superfluous) == 1:
                    candidates.append(s)
            if len(candidates) > 1:
                print(
                    f'Cannot fix name of assignment {m} for {student} because there is more than one matching file'
                )
            elif len(candidates) == 1:
                c = candidates[0]
                # Windows
                shell.run(['mv', '-i', shell.pjoin(d, c), shell.pjoin(d, m)])
Пример #14
0
def copyTemplate(studentDir: str, studentId: str, path: str, copy: bool):
    (b, e) = shell.splitExt(shell.basename(path))
    for t in ['_TEMPLATE_', 'TEMPLATE_', '_TEMPLATE', 'TEMPLATE']:
        b = b.replace(t, '')
    b = b + '_' + studentId
    newPath = shell.pjoin(studentDir, b) + e
    if not shell.isFile(newPath):
        if copy:
            note(f"Copying template {path} to {newPath}")
            shell.cp(path, newPath)
            spreadsheet.replaceData(newPath, 'ID', 'STUDENT_ID', studentId)
        else:
            return None
    return newPath
Пример #15
0
def addComment(cfg):
    submissionDirs = collectSubmissionDirs(cfg)
    print(submissionDirs)
    for d in submissionDirs:
        p = shell.pjoin(d, cfg.commentsFile)
        verbose(f'Writing file {p}')
        writeFile(
            p, """In dieser Datei stehen allgemeine Kommentare zur Abgabe.

Die Bewertung finden Sie in der Datei POINTS.txt.

Möglicherweise enthalten die Quelldateien aufgaben-spezifische Kommentare. Diese sind mit TUTOR/TUTORIN oder
DOZENT/DOZENTIN gekennzeichnet.

=============================================================================================================

""")
Пример #16
0
def spreadsheetTest(action,
                    check=None,
                    path='test-data/bewertung.xlsx',
                    sheetName=None):
    with shell.tempDir(delete=False, dir='.') as d:
        name = f'test_{uuid.uuid4()}.xlsx'
        p = shell.pjoin(d, name)
        shell.cp(path, p)
        x = action(p)
        if check:
            wb = exc.load_workbook(filename=p)
            if sheetName:
                sheet = wb[sheetName]
            else:
                sheet = wb.active
            check(sheet)
        return x
Пример #17
0
def _runJavaTest(ctx, studentDir: str, codeDir: str, assignment: Assignment,
                 testId: str, testDir: str, filter: Optional[str],
                 hasTests: bool, isStudent: bool):
    cfg = ctx.cfg
    if filter is None:
        filter = '*'
    gradleProps = {
        'testFilter': filter,
        'testDir': testDir,
        'studentDir': codeDir
    }
    gradlePropArgs = []
    for k, v in gradleProps.items():
        gradlePropArgs.append(f'-P{k}={v}')
    print()
    print(blue(f"Starting test {testId}"))
    with shell.workingDir(cfg.baseDir):
        if not shell.isFile('build.gradle'):
            abort(f'No build.gradle file in {cfg.baseDir}, aborting')
        if not hasTests:
            gradleCmd = 'compileJava'
        else:
            gradleCmd = 'test'
        cmd = [cfg.gradlePath] + gradlePropArgs + [gradleCmd, '--rerun-tasks']
        print(f'Executing {" ".join(cmd)}')
        logFileName = shell.pjoin(studentDir, f'OUTPUT_{testId}.txt')
        with shell.createTee([shell.TEE_STDOUT, logFileName]) as tee:
            result = shell.run(cmd,
                               onError='ignore',
                               stderrToStdout=True,
                               captureStdout=tee)
        output = open(logFileName, 'r').read()
    if result.exitcode == 0:
        print(green(f'Test {testId} OK'))
    else:
        print(red(f'Test {testId} FAILED, see above'))
    result = Result.parseResult(output)
    prefix = 'S' if isStudent else ''
    ctx.storeTestResultInSpreadsheet(studentDir, assignment, testId,
                                     [prefix + 'C'],
                                     0 if result.compileError else 1)
    if hasTests:
        ctx.storeTestResultInSpreadsheet(studentDir, assignment, testId,
                                         [prefix + 'T'], result.ratio())
Пример #18
0
def addComment(cfg):
    submissionDirs = collectSubmissionDirs(cfg)
    print(submissionDirs)
    for d in submissionDirs:
        p = shell.pjoin(d, cfg.commentsFile)
        verbose(f'Writing file {p}')
        writeFile(p, """In dieser Datei stehen allgemeine Kommentare zur Abgabe.

Die Bewertung finden Sie in der Datei POINTS.txt.

Falls für Ihre Abgabe automatisch Tests ausgeführt wurden, finden Sie die
Ausgabe der Tests für die i-te Aufgabe in der Datei OUTPUT_i.txt
bzw. OUTPUT_student_i.txt (für Ihre eigenen Tests).

Möglicherweise enthalten die Quelldateien aufgaben-spezifische Kommentare.
Diese sind mit TUTOR/TUTORIN oder DOZENT/DOZENTIN gekennzeichnet, so dass
Sie bequem danach suchen können.

=============================================================================================================

""")
Пример #19
0
 def parse(baseDir,
           assignmentId: int,
           id: Optional[str],
           dicts: list[dict],
           fail: bool = True):
     dir = getFromDicts(dicts, 'test-dir', default=defaultTestDir(baseDir))
     file = getFromDicts(dicts, 'test-file', fail=False)
     if file:
         file = shell.pjoin(dir, file)
     filter = getFromDicts(dicts, 'test-filter', fail=False)
     testId = str(assignmentId)
     if id:
         testId = f'{testId}_{id}'
     if not file and not filter:
         if fail:
             testName = f'test "{id}"' if id else 'test'
             raise ValueError(
                 f"Invalid {testName} without file and without filter")
         else:
             return None
     return Test(testId, dir, filter, file)
Пример #20
0
#!/usr/bin/env python

import sys
import os
import zipfile
thisDir = os.path.dirname(__file__)
topDir = os.path.join(thisDir, '..')
sys.path.insert(0, os.path.join(topDir, 'src'))
import shell
import openpyxl as exc

checkAssignments = shell.abspath(shell.pjoin(topDir, 'check-assignments'))
if sys.platform == 'win32':
    checkAssignments = checkAssignments + ".bat"


def abort(msg):
    sys.stderr.write(msg + '\n')
    sys.exit(1)


def assertDirExists(path):
    if not shell.isDir(path):
        d = shell.dirname(path)
        files = shell.ls(d, '*')
        abort(f'File {path} does not exist, existing files: {files}')


def assertExists(path):
    if not shell.isFile(path):
        d = shell.dirname(path)
Пример #21
0
def defaultTestDir(baseDir):
    return shell.pjoin(baseDir, 'tests')
Пример #22
0
def defaultRatingSheet(baseDir):
    return shell.pjoin(baseDir, 'rating.xlsx')
Пример #23
0
def runJplagMerged(lang, kind, ext, args):
    print()
    print(ansi.green(f'Running jplag for all assignments of kind {kind}'))
    runJplag(
        ['-r', shell.pjoin(resultDir, kind), '-l', lang, '-p', ext, '.'], args,
        '*' + ext)
Пример #24
0
 def getAsFileList(self, d, k):
     items = self.getAsList(k)
     return [shell.pjoin(d, x) for x in items]
Пример #25
0
 def outputFile(self, studentDir):
     return shell.pjoin(studentDir, f'OUTPUT_{self.id}.txt')
Пример #26
0
 def pointsTemplate(self):
     return shell.pjoin(self.baseDir, 'POINTS_template.txt')
Пример #27
0
 def ratingCsvPath(self):
     return shell.pjoin(self.baseDir, 'rating.csv')
Пример #28
0
 def spreadsheetPath(self):
     return shell.pjoin(self.baseDir, 'rating.xlsx')
Пример #29
0
#!/usr/bin/env python

import sys
import os
import zipfile
thisDir = os.path.dirname(__file__)
topDir = os.path.join(thisDir, '..')
sys.path.insert(0, os.path.join(topDir, 'src'))
import shell

checkAssignments = shell.abspath(shell.pjoin(topDir, 'check-assignments'))
if sys.platform == 'win32':
    checkAssignments = checkAssignments + ".bat"

def abort(msg):
    sys.stderr.write(msg + '\n')
    sys.exit(1)

def assertExists(path):
    if not shell.isFile(path):
        abort(f'File {path} does not exist')

with shell.tempDir(onException=False) as tmp:
    print(f'tmp={tmp}')
    shell.cp(shell.pjoin(topDir, 'test-data'), tmp)
    with shell.workingDir(shell.pjoin(tmp, 'test-data/submissions')):

        print('### import ###')
        shell.run([checkAssignments, 'import', '../rating-moodle.csv'])
        assertExists('rating.xlsx')
Пример #30
0
 def fun(c):
     fixFilenames(c)
     studDir = shell.pjoin(c.baseDir, student)
     existingFiles = [shell.basename(p) for p in shell.ls(studDir)]
     self.assertEqual(sorted(expectedFiles), sorted(existingFiles))