From b648cbdfa1c7f6035bc9359274baf6fbe13bc135 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 | 71 +++++++++++++----- helpers/Dojo/ClientsSharedAssignmentHelper.ts | 39 +++++++++- models/Assignment.ts | 74 ++++++++++++++++++- 3 files changed, 162 insertions(+), 22 deletions(-) diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts index 1a92994..1d2743b 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.js'; -import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper.js'; -import path from 'node:path'; -import AssignmentCheckerError from '../../../shared/types/Dojo/AssignmentCheckerError.js'; -import fs from 'fs-extra'; -import YAML from 'yaml'; -import DojoDockerCompose from '../../types/Dojo/DojoDockerCompose.js'; -import { exec, spawn } from 'child_process'; -import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile.js'; -import ExerciseDockerCompose from './ExerciseDockerCompose.js'; -import util from 'util'; -import ClientsSharedConfig from '../../config/ClientsSharedConfig'; +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); @@ -35,6 +37,7 @@ class AssignmentValidator { private dockerComposeFile!: DojoDockerCompose; private assignmentFile!: AssignmentFile; + private assignment!: Assignment; constructor(folderAssignment: string, doDown: boolean = false) { this.folderAssignment = folderAssignment; @@ -123,9 +126,31 @@ class AssignmentValidator { } /** - * Step 2: dojo_assignment.json file validation + * Step 2: Check assignment + * - Check if assignment exists in backend + * @private + */ + private async checkAssignment() { + 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 { + this.assignment = resp; + } + this.endSubStep('Assignment exists', false); + + this.endStep('Assignment exists and is valid', false); + } + + /** + * 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) * @private */ private dojoAssignmentFileValidation() { @@ -164,12 +189,22 @@ 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(this.assignment.language) && this.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 * @private @@ -223,7 +258,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 * @private @@ -246,7 +281,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) * @private */ @@ -292,6 +327,8 @@ class AssignmentValidator { try { await this.checkRequirements(); + await this.checkAssignment(); + this.dojoAssignmentFileValidation(); await this.dockerComposeFileValidation(); diff --git a/helpers/Dojo/ClientsSharedAssignmentHelper.ts b/helpers/Dojo/ClientsSharedAssignmentHelper.ts index 99d956a..b7672f1 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.js'; -import AssignmentValidator from './AssignmentValidator.js'; +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 { @@ -22,6 +29,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 5d8f03e..9e447ec 100644 --- a/models/Assignment.ts +++ b/models/Assignment.ts @@ -11,6 +11,8 @@ interface Assignment { gitlabLastInfo: Gitlab.ProjectSchema; 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