From 2b574d9a21ba172a72280ccf909a816407f24623 Mon Sep 17 00:00:00 2001 From: Joel von der Weid <joel.von-der-weid@hesge.ch> Date: Mon, 25 Mar 2024 13:19:30 +0100 Subject: [PATCH] Add check buildline in assignment check --- helpers/Dojo/AssignmentValidator.ts | 69 ++++++++++++----- helpers/Dojo/ClientsSharedAssignmentHelper.ts | 39 +++++++++- models/Assignment.ts | 74 ++++++++++++++++++- 3 files changed, 159 insertions(+), 23 deletions(-) diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts index 6ecec2c..e5e86c5 100644 --- a/helpers/Dojo/AssignmentValidator.ts +++ b/helpers/Dojo/AssignmentValidator.ts @@ -1,16 +1,18 @@ -import { TypedEmitter } from 'tiny-typed-emitter'; -import AssignmentValidatorEvents from '../../types/Dojo/AssignmentValidatorEvents'; -import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; -import path from 'node:path'; -import AssignmentCheckerError from '../../../shared/types/Dojo/AssignmentCheckerError'; -import fs from 'fs-extra'; -import ClientsSharedConfig from '../../config/ClientsSharedConfig'; -import YAML from 'yaml'; -import DojoDockerCompose from '../../types/Dojo/DojoDockerCompose'; -import { exec, spawn } from 'child_process'; -import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile'; -import ExerciseDockerCompose from './ExerciseDockerCompose'; -import util from 'util'; +import { TypedEmitter } from 'tiny-typed-emitter'; +import AssignmentValidatorEvents from '../../types/Dojo/AssignmentValidatorEvents'; +import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; +import path from 'node:path'; +import AssignmentCheckerError from '../../../shared/types/Dojo/AssignmentCheckerError'; +import fs from 'fs-extra'; +import ClientsSharedConfig from '../../config/ClientsSharedConfig'; +import YAML from 'yaml'; +import DojoDockerCompose from '../../types/Dojo/DojoDockerCompose'; +import { exec, spawn } from 'child_process'; +import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile'; +import ExerciseDockerCompose from './ExerciseDockerCompose'; +import util from 'util'; +import Assignment, { Language } from '../../models/Assignment'; +import ClientsSharedAssignmentHelper from './ClientsSharedAssignmentHelper'; const execAsync = util.promisify(exec); @@ -81,7 +83,7 @@ class AssignmentValidator { (async () => { let dockerComposeFile: DojoDockerCompose; let assignmentFile: AssignmentFile; - + let assignment: Assignment; /* //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: Check requirements @@ -118,11 +120,32 @@ class AssignmentValidator { this.endStep('All requirements are satisfied', false); } + /* + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: Check assignment + - Check if assignment exists in backend + */ + { + this.newStep('ASSIGNMENT_CHECKING', 'Please wait while we are checking the assignment...'); + + this.newSubStep('ASSIGNMENT_EXISTS', 'Checking if the assignment exists'); + const resp = await ClientsSharedAssignmentHelper.getAssignmentFromPath(this.folderAssignment); + if (resp == undefined) { + this.emitError(`The assignment doesn't exist. An assignment must be created with "assignment create" before checking it.`, `Assignment doesn't exists`, AssignmentCheckerError.ASSIGNMENT_MISSING); + return; + } else { + assignment = resp; + } + this.endSubStep('Assignment exists', false); + + this.endStep('Assignment exists and is valid', false); + } + /* - //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: dojo_assignment.json file validation + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: dojo_assignment.json file validation - Structure validation - Immutable files validation (Check if exists and if the given type is correct) + - Build line validation (for C-derived languages and sonar activated projects) */ { this.newStep('ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...'); @@ -159,13 +182,23 @@ class AssignmentValidator { } this.endSubStep('Immutable files are valid', false); + // Build line validation (only if language is C/CPP/OBJ-C and sonar activated) + if ([Language.c, Language.cpp, Language.objc].includes(assignment.language) && assignment.useSonar) { + this.newSubStep('ASSIGNMENT_FILE_BUILD_LINE_VALIDATION', 'Validating build line'); + const build = validationResults.content!.buildLine; + if (build == undefined || build.trim() == "") { + this.emitError(`BuildLine is required for this language`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.BUILD_LINE_MISSING); + return; + } + this.endSubStep('Build line is valid', false); + } this.endStep('dojo_assignment.json file is valid', false); } /* - //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Docker Compose file validation + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Docker Compose file validation - Global validation - Validation of the containers and volumes named in dojo_assignment.json */ @@ -218,7 +251,7 @@ class AssignmentValidator { /* - //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Dockerfiles validation + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 5: Dockerfiles validation - Check if file exists - TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint */ @@ -241,7 +274,7 @@ class AssignmentValidator { /* - //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 5: Run + //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 6: Run - Make a run of the assignment (If the return code is 0, the assignment is not valid because it means that there no need of modification for succeed the exercise) */ { diff --git a/helpers/Dojo/ClientsSharedAssignmentHelper.ts b/helpers/Dojo/ClientsSharedAssignmentHelper.ts index c47d2b4..7bb900d 100644 --- a/helpers/Dojo/ClientsSharedAssignmentHelper.ts +++ b/helpers/Dojo/ClientsSharedAssignmentHelper.ts @@ -1,7 +1,14 @@ -import chalk from 'chalk'; -import boxen from 'boxen'; -import Icon from '../../../shared/types/Icon'; -import AssignmentValidator from './AssignmentValidator'; +import { existsSync, readFileSync } from 'fs'; +import { join } from 'path'; +import chalk from 'chalk'; +import boxen from 'boxen'; +import Icon from '../../../shared/types/Icon'; +import AssignmentValidator from './AssignmentValidator'; +import Assignment from '../../models/Assignment'; +import axios from 'axios'; +import DojoBackendResponse from '../../../shared/types/Dojo/DojoBackendResponse'; +import ApiRoute from '../../types/Dojo/ApiRoute'; +import ClientsSharedConfig from '../../config/ClientsSharedConfig'; class ClientsSharedAssignmentHelper { @@ -21,6 +28,30 @@ class ClientsSharedAssignmentHelper { textAlignment : 'left' })); } + + private async getAssignment(url: string): Promise<Assignment | undefined> { + try { + return (await axios.get<DojoBackendResponse<Assignment>>(`${ ClientsSharedConfig.apiURL }${ ApiRoute.ASSIGNMENT_GET }`.replace('{{nameOrUrl}}', encodeURIComponent(url)))).data.data; + } catch ( error ) { + return undefined; + } + } + + private async extractOriginUrl(content: string): Promise<string> { + const regexp = /\[remote "origin"]\r?\n\s*url\s*=\s*(.*)\s*\n/gm; + return Array.from(content.matchAll(regexp), m => m[1])[0]; + } + + async getAssignmentFromPath(path: string): Promise<Assignment | undefined> { + const fullPath = join(path, "./.git/config"); + if (!existsSync(fullPath)) { + return undefined; + } + const content = readFileSync(fullPath, 'utf-8'); + const url = await this.extractOriginUrl(content); + + return await this.getAssignment(url); + } } diff --git a/models/Assignment.ts b/models/Assignment.ts index c56f425..5b67023 100644 --- a/models/Assignment.ts +++ b/models/Assignment.ts @@ -11,6 +11,8 @@ interface Assignment { gitlabLastInfo: GitlabRepository; gitlabLastInfoDate: string; published: boolean; + useSonar: boolean; + language: Language; staff: Array<User>; exercises: Array<Exercise>; @@ -19,4 +21,74 @@ interface Assignment { } -export default Assignment; \ No newline at end of file +export enum Language { + abap = "abap", + ada = "ada", + asm = "asm", + bash = "bash", + bqn = "bqn", + c = "c", + caml = "caml", + cloudformation = "cloudformation", + cpp = "cpp", + csharp = "csharp", + css = "css", + cuda = "cuda", + dart = "dart", + delphi = "delphi", + docker = "docker", + erlang = "erlang", + f = "f", + fsharp = "fsharp", + flex = "flex", + fortran = "fortran", + futhark = "futhark", + go = "go", + groovy = "groovy", + haskell = "haskell", + hepial = "hepial", + json = "json", + jsp = "jsp", + java = "java", + js = "js", + julia = "julia", + kotlin = "kotlin", + kubernetes = "kubernetes", + latex = "latex", + lisp = "lisp", + lua = "lua", + matlab = "matlab", + objc = "objc", + ocaml = "ocaml", + pascal = "pascal", + pearl = "pearl", + perl = "perl", + php = "php", + postscript = "postscript", + powershell = "powershell", + prolog = "prolog", + promela = "promela", + python = "python", + r = "r", + ruby = "ruby", + rust = "rust", + scala = "scala", + sql = "sql", + smalltalk = "smalltalk", + swift = "swift", + terraform = "terraform", + text = "text", + ts = "ts", + tsql = "tsql", + typst = "typst", + vba = "vba", + vbnet = "vbnet", + web = "web", + xml = "xml", + yaml = "yaml", + + other = "other" +} + + +export default Assignment; -- GitLab