├── simplecodetester-frontend ├── src │ ├── vuetify.d.ts │ ├── shims-vue.d.ts │ ├── assets │ │ ├── logo.png │ │ └── Github_Mark.png │ ├── scss │ │ └── variables.scss │ ├── store │ │ ├── modules │ │ │ ├── miscsettings │ │ │ │ ├── actions.ts │ │ │ │ ├── mutations.ts │ │ │ │ └── index.ts │ │ │ ├── checkresult │ │ │ │ ├── mutations.ts │ │ │ │ └── index.ts │ │ │ ├── checkcategories │ │ │ │ ├── index.ts │ │ │ │ ├── mutations.ts │ │ │ │ └── actions.ts │ │ │ └── user │ │ │ │ ├── mutations.ts │ │ │ │ ├── actions.ts │ │ │ │ └── index.ts │ │ └── index.ts │ ├── shims-tsx.d.ts │ ├── components │ │ ├── crud │ │ │ ├── CrudTypes.ts │ │ │ └── CrudModifyActions.vue │ │ ├── CheckCategorySelection.vue │ │ ├── ThemeSelector.vue │ │ ├── checksubmit │ │ │ ├── CheckSubmitErrorDialog.vue │ │ │ └── IOCheckComponent.vue │ │ ├── users │ │ │ ├── ChangeOwnPassword.vue │ │ │ ├── ChangePassword.vue │ │ │ └── UserModificationComponent.vue │ │ ├── upload │ │ │ └── MultiFileSelect.vue │ │ ├── highlighting │ │ │ └── HighlightedCode.vue │ │ └── Profile.vue │ ├── main.ts │ ├── util │ │ ├── requests.ts │ │ └── Highlighting.ts │ ├── plugins │ │ └── vuetify.ts │ └── App.vue ├── .jsbeautifyrc ├── .env.development ├── .env.production ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── vue.config.js ├── .gitignore ├── README.md ├── tsconfig.json └── package.json ├── media ├── logo.png ├── check-result-light.png ├── sample-check-dark.png └── sample-check-light.png ├── .gitignore ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── SimpleCodeTester-Runner ├── sandbox │ ├── kill-container.sh │ ├── Dockerfile │ └── start-container.sh ├── src │ ├── main │ │ ├── resources │ │ │ └── example_config.json │ │ └── java │ │ │ └── me │ │ │ └── ialistannen │ │ │ └── simplecodetester │ │ │ └── runner │ │ │ ├── execution │ │ │ ├── StreamsProcessOutput.java │ │ │ └── WaitingFutureTask.java │ │ │ ├── util │ │ │ ├── ThreadUtil.java │ │ │ └── ProgramResult.java │ │ │ └── config │ │ │ └── RunnerConfiguration.java │ └── test │ │ └── java │ │ └── me │ │ └── ialistannen │ │ └── simplecodetester │ │ └── runner │ │ └── util │ │ └── ThreadUtilTest.java └── pom.xml ├── SimpleCodeTester-Backend └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── ialistannen │ │ │ └── simplecodetester │ │ │ └── backend │ │ │ ├── exception │ │ │ ├── CheckRunningFailedException.java │ │ │ ├── CheckParseException.java │ │ │ ├── CodeTesterException.java │ │ │ ├── WebStatusCodeException.java │ │ │ └── WebStatusExceptionMapperAdvice.java │ │ │ ├── endpoints │ │ │ ├── checks │ │ │ │ └── parsers │ │ │ │ │ ├── CheckParser.java │ │ │ │ │ ├── StaticInputOutputCheckParser.java │ │ │ │ │ ├── InterleavedIoCheckParser.java │ │ │ │ │ └── CheckParsers.java │ │ │ ├── CheckCategoryEndpoint.java │ │ │ └── runner │ │ │ │ └── RunnerEndpoint.java │ │ │ ├── services │ │ │ ├── config │ │ │ │ ├── ConfigurationService.java │ │ │ │ ├── ParsingConfig.java │ │ │ │ ├── DatabaseConfig.java │ │ │ │ └── RunnerConfig.java │ │ │ ├── checks │ │ │ │ └── CheckCategoryService.java │ │ │ └── user │ │ │ │ └── UserService.java │ │ │ ├── util │ │ │ ├── CheckedConsumer.java │ │ │ ├── CheckedFunction.java │ │ │ └── ResponseUtil.java │ │ │ ├── db │ │ │ ├── entities │ │ │ │ ├── CheckCategory.java │ │ │ │ ├── User.java │ │ │ │ └── CodeCheck.java │ │ │ ├── storage │ │ │ │ ├── DBWriteAccess.java │ │ │ │ └── DBReadAccess.java │ │ │ └── repos │ │ │ │ └── CheckCategoryRepository.java │ │ │ ├── security │ │ │ ├── JwtGenerator.java │ │ │ ├── AuthenticatedJwtUser.java │ │ │ └── JwtFilter.java │ │ │ └── Main.java │ └── resources │ │ ├── db │ │ └── migration │ │ │ └── V1__Basic_Scherma.sql │ │ └── config │ │ └── application.yml │ └── test │ ├── resources │ └── config │ │ └── application.yml │ └── java │ └── me │ └── ialistannen │ └── simplecodetester │ └── backend │ └── services │ └── user │ └── UserServiceTest.java ├── SimpleCodeTester-Lib ├── src │ ├── main │ │ └── java │ │ │ ├── me │ │ │ └── ialistannen │ │ │ │ └── simplecodetester │ │ │ │ ├── checks │ │ │ │ ├── CheckType.java │ │ │ │ ├── defaults │ │ │ │ │ └── io │ │ │ │ │ │ ├── parsing │ │ │ │ │ │ ├── CommentParser.java │ │ │ │ │ │ ├── ErrorParser.java │ │ │ │ │ │ ├── LiteralParser.java │ │ │ │ │ │ ├── VerbatimParser.java │ │ │ │ │ │ ├── RegexParser.java │ │ │ │ │ │ └── Parser.java │ │ │ │ │ │ ├── matcher │ │ │ │ │ │ ├── InterleavedIoMatcher.java │ │ │ │ │ │ ├── CommentIoMatcher.java │ │ │ │ │ │ ├── ErrorIoMatcher.java │ │ │ │ │ │ ├── LiteralIoMatcher.java │ │ │ │ │ │ ├── VerbatimInputMatcher.java │ │ │ │ │ │ └── RegularExpressionIoMatcher.java │ │ │ │ │ │ ├── LineResult.java │ │ │ │ │ │ ├── ResultBlock.java │ │ │ │ │ │ ├── Block.java │ │ │ │ │ │ └── MatcherBlock.java │ │ │ │ ├── SubmissionCheckResult.java │ │ │ │ ├── storage │ │ │ │ │ └── CheckSerializer.java │ │ │ │ └── Check.java │ │ │ │ ├── exceptions │ │ │ │ ├── UnsupportedIoException.java │ │ │ │ ├── CompiledClassNotLoadableException.java │ │ │ │ ├── ReadMoreLinesThanProvidedException.java │ │ │ │ ├── SuppressStacktrace.java │ │ │ │ ├── CheckFailedException.java │ │ │ │ └── CompilationException.java │ │ │ │ ├── submission │ │ │ │ ├── Submission.java │ │ │ │ ├── CompleteTask.java │ │ │ │ ├── CompiledSubmission.java │ │ │ │ └── CompiledFile.java │ │ │ │ ├── util │ │ │ │ ├── ErrorLogCapture.java │ │ │ │ ├── StringOutputStream.java │ │ │ │ ├── ClassParsingUtil.java │ │ │ │ ├── MainClassDetectionUtil.java │ │ │ │ └── ExceptionUtil.java │ │ │ │ ├── result │ │ │ │ └── Result.java │ │ │ │ └── compilation │ │ │ │ └── CompilationOutput.java │ │ │ └── edu │ │ │ └── kit │ │ │ └── kastel │ │ │ └── trafficsimulation │ │ │ └── io │ │ │ └── SimulationFileLoader.java │ └── test │ │ └── java │ │ ├── me │ │ └── ialistannen │ │ │ └── simplecodetester │ │ │ ├── checks │ │ │ └── defaults │ │ │ │ └── io │ │ │ │ ├── parsing │ │ │ │ ├── ErrorParserTest.java │ │ │ │ ├── CommentParserTest.java │ │ │ │ ├── RegexParserTest.java │ │ │ │ ├── LiteralParserTest.java │ │ │ │ └── VerbatimParserTest.java │ │ │ │ ├── matcher │ │ │ │ ├── LiteralIoMatcherTest.java │ │ │ │ ├── VerbatimInputMatcherTest.java │ │ │ │ ├── RegularExpressionIoMatcherTest.java │ │ │ │ ├── CommentIoMatcherTest.java │ │ │ │ └── ErrorIoMatcherTest.java │ │ │ │ ├── BlockTest.java │ │ │ │ └── ResultBlockTest.java │ │ │ └── util │ │ │ ├── StringOutputStreamTest.java │ │ │ ├── ErrorLogCaptureTest.java │ │ │ ├── ClassParsingUtilTest.java │ │ │ └── ConfiguredGsonTest.java │ │ └── edu │ │ └── kit │ │ └── informatik │ │ └── TerminalTest.java └── pom.xml ├── SimpleCodeTester-Executor ├── src │ └── main │ │ └── java │ │ └── me │ │ └── ialistannen │ │ └── simplecodetester │ │ └── execution │ │ ├── diana │ │ ├── TerminalFiles.java │ │ ├── TerminalFileReader.java │ │ ├── TerminalBufferedReader.java │ │ ├── TerminalScanner.java │ │ └── Rewriter.java │ │ ├── compilation │ │ ├── Compiler.java │ │ └── memory │ │ │ ├── InMemoryOutputObject.java │ │ │ ├── InMemoryFileInputObject.java │ │ │ └── ClassFileManager.java │ │ ├── UntrustedMain.java │ │ └── security │ │ └── ExecutorSandboxPolicy.java └── pom.xml ├── .github └── workflows │ └── build.yml └── README.md /simplecodetester-frontend/src/vuetify.d.ts: -------------------------------------------------------------------------------- 1 | declare module "vuetify/lib"; -------------------------------------------------------------------------------- /simplecodetester-frontend/.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "wrap_attributes": "auto" 3 | } -------------------------------------------------------------------------------- /simplecodetester-frontend/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_URL="http://localhost:8081" 2 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/media/logo.png -------------------------------------------------------------------------------- /simplecodetester-frontend/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_URL="https://codetester.ialistannen.de" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/* 3 | target/ 4 | *.db 5 | *.db-wal 6 | *.db-shm 7 | 8 | !/.idea/codeStyles 9 | -------------------------------------------------------------------------------- /simplecodetester-frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /media/check-result-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/media/check-result-light.png -------------------------------------------------------------------------------- /media/sample-check-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/media/sample-check-dark.png -------------------------------------------------------------------------------- /media/sample-check-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/media/sample-check-light.png -------------------------------------------------------------------------------- /simplecodetester-frontend/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /simplecodetester-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/simplecodetester-frontend/public/favicon.ico -------------------------------------------------------------------------------- /simplecodetester-frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/simplecodetester-frontend/src/assets/logo.png -------------------------------------------------------------------------------- /simplecodetester-frontend/src/assets/Github_Mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/I-Al-Istannen/SimpleCodeTester/HEAD/simplecodetester-frontend/src/assets/Github_Mark.png -------------------------------------------------------------------------------- /simplecodetester-frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "configureWebpack": { 3 | "plugins": [] 4 | }, 5 | "transpileDependencies": [ 6 | "vuetify" 7 | ] 8 | } -------------------------------------------------------------------------------- /simplecodetester-frontend/src/scss/variables.scss: -------------------------------------------------------------------------------- 1 | $material-dark: ( 2 | "background": #2f3136, 3 | "cards": #2f3136, 4 | "text": ( 5 | "primary": rgba(255,255,255, 0.87) 6 | ) 7 | ); -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/sandbox/kill-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | if [ "$#" -ne 1 ]; then 5 | echo "Usage: $0 " 6 | fi 7 | 8 | docker kill -s SIGKILL "codetester-$1" 9 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/miscsettings/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex'; 2 | import { MiscSettingsState } from '../../types'; 3 | import { RootState } from '../../types'; 4 | 5 | export const actions: ActionTree = {}; -------------------------------------------------------------------------------- /simplecodetester-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/exception/CheckRunningFailedException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.exception; 2 | 3 | public class CheckRunningFailedException extends CodeTesterException { 4 | 5 | public CheckRunningFailedException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/main/resources/example_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "container_start_command": [ 3 | "sudo", 4 | "start-container.sh" 5 | ], 6 | "container_kill_command": [ 7 | "sudo", 8 | "kill-container.sh" 9 | ], 10 | "max_runtime_seconds": 30, 11 | "backend_url": "http://localhost:8081", 12 | "backend_password": "foobar" 13 | } 14 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/exception/CheckParseException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.exception; 2 | 3 | /** 4 | * An exception while parsing a check. 5 | */ 6 | public class CheckParseException extends RuntimeException { 7 | 8 | public CheckParseException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/CheckType.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks; 2 | 3 | public enum CheckType { 4 | /** 5 | * A check that checks program in and output. 6 | */ 7 | IO, 8 | /** 9 | * A check that just verifies the imports. 10 | */ 11 | INTERLEAVED_IO, 12 | /** 13 | * A check of unknown type. 14 | */ 15 | UNKNOWN, 16 | } 17 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/UnsupportedIoException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | /** 4 | * An exception indicating that I/O is not supported. 5 | */ 6 | @SuppressStacktrace 7 | public class UnsupportedIoException extends RuntimeException { 8 | 9 | public UnsupportedIoException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/checkresult/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex'; 2 | import { CheckResultState, CheckResult } from '../../types'; 3 | 4 | export const mutations: MutationTree = { 5 | checkResult(state: CheckResultState, payload: CheckResult) { 6 | state.checkResult = payload; 7 | }, 8 | clear(state: CheckResultState) { 9 | state.checkResult = null 10 | } 11 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/CompiledClassNotLoadableException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | public class CompiledClassNotLoadableException extends RuntimeException { 4 | 5 | public CompiledClassNotLoadableException(String qualifiedName, Throwable cause) { 6 | super(String.format("The class '%s' was not loadable.", qualifiedName), cause); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/exception/CodeTesterException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.exception; 2 | 3 | public class CodeTesterException extends RuntimeException { 4 | 5 | public CodeTesterException(String message) { 6 | super(message); 7 | } 8 | 9 | public CodeTesterException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/ReadMoreLinesThanProvidedException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | /** 4 | * Indicates the program read more lines than were provided. 5 | */ 6 | public class ReadMoreLinesThanProvidedException extends RuntimeException { 7 | 8 | public ReadMoreLinesThanProvidedException() { 9 | super("No input present but you tried to read!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/miscsettings/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex'; 2 | import { MiscSettingsState, CheckCategory } from '../../types'; 3 | 4 | export const mutations: MutationTree = { 5 | setItemsPerPage(state: MiscSettingsState, items: number) { 6 | state.itemsPerPage = items; 7 | }, 8 | setCategory(state: MiscSettingsState, category: CheckCategory | null) { 9 | state.category = category; 10 | } 11 | }; -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/checkresult/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { RootState, CheckResultState } from '../../types' 3 | import { mutations } from './mutations' 4 | import { actions } from './actions' 5 | 6 | export const state: CheckResultState = { 7 | checkResult: null 8 | }; 9 | 10 | const namespaced: boolean = true; 11 | 12 | export const checkresult: Module = { 13 | namespaced, 14 | state, 15 | mutations, 16 | actions 17 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/sandbox/Dockerfile: -------------------------------------------------------------------------------- 1 | # We are currently using Java 11 2 | FROM openjdk:11-slim 3 | 4 | # The container is run with lower privileges -> Set up the necessary user 5 | RUN useradd --uid 10000 -m codetester 6 | 7 | # Add the Executor jar to the image 8 | COPY SimpleCodeTester-Executor.jar /home/codetester/Executor.jar 9 | 10 | # Drop down to the codetester unprivileged user 11 | USER codetester 12 | 13 | # And run the executor! 14 | ENTRYPOINT ["java", "-jar", "/home/codetester/Executor.jar"] 15 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/checkcategories/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { RootState, CheckCategoryState } from '../../types' 3 | import { mutations } from './mutations' 4 | import { actions } from './actions' 5 | 6 | export const state: CheckCategoryState = { 7 | categories: [] 8 | }; 9 | 10 | const namespaced: boolean = true; 11 | 12 | export const checkcategory: Module = { 13 | namespaced, 14 | state, 15 | mutations, 16 | actions 17 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/test/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | jwt.secret.key: "dsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsds" 2 | 3 | runner: 4 | classpath: "/home/i_al_istannen/Programming/Uni/SimpleCodeTester/SimpleCodeTester-Lib/target/SimpleCodeTester-Lib.jar" 5 | max-computation-time-seconds: 30 6 | 7 | parsing: 8 | minCommands: 0 9 | 10 | cors: 11 | allowedOrigin: 12 | - "http://localhost:8080" 13 | 14 | database: 15 | url: "jdbc:sqlite:target/unit-test.db" 16 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/SuppressStacktrace.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Suppresses the stacktrace of an exception in the output. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface SuppressStacktrace { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /simplecodetester-frontend/README.md: -------------------------------------------------------------------------------- 1 | # simplecodetester-frontend 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/miscsettings/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex' 2 | import { RootState, MiscSettingsState } from '../../types' 3 | import { mutations } from './mutations' 4 | import { actions } from './actions' 5 | import { isJwtValid } from '@/util/requests'; 6 | 7 | export const state: MiscSettingsState = { 8 | itemsPerPage: 10, 9 | category: null 10 | }; 11 | 12 | const namespaced: boolean = true; 13 | 14 | export const miscsettings: Module = { 15 | namespaced, 16 | state, 17 | mutations, 18 | actions 19 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/checks/parsers/CheckParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints.checks.parsers; 2 | 3 | import me.ialistannen.simplecodetester.checks.Check; 4 | 5 | /** 6 | * A parser for a check. 7 | * 8 | * @param the type of the check 9 | */ 10 | public interface CheckParser { 11 | 12 | /** 13 | * Parses the given input to a check. 14 | * 15 | * @param payload the input payload 16 | * @return the parsed result 17 | */ 18 | T parse(String payload); 19 | } 20 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/submission/Submission.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.submission; 2 | 3 | import java.util.Map; 4 | import org.immutables.gson.Gson; 5 | import org.immutables.value.Value; 6 | 7 | /** 8 | * A collection of files that were submitted for checking. 9 | */ 10 | @Value.Immutable 11 | @Gson.TypeAdapters 12 | public abstract class Submission { 13 | 14 | /** 15 | * All files in this submission. A mapping from FQN (in java file syntax, 16 | * so "de/ialistannen/Foo.java" -> Content. 17 | */ 18 | public abstract Map files(); 19 | } 20 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/exception/WebStatusCodeException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class WebStatusCodeException extends CodeTesterException { 6 | 7 | private final HttpStatus status; 8 | 9 | public WebStatusCodeException(String message, HttpStatus status) { 10 | super(message); 11 | this.status = status; 12 | } 13 | 14 | /** 15 | * @return the {@link HttpStatus} to set 16 | */ 17 | public HttpStatus getStatus() { 18 | return status; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/test/java/me/ialistannen/simplecodetester/runner/util/ThreadUtilTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.runner.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import java.util.concurrent.ThreadFactory; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class ThreadUtilTest { 9 | 10 | @Test 11 | void daemonCreatesIsDaemons() { 12 | ThreadFactory threadFactory = ThreadUtil.daemonThreadFactory(Thread::new); 13 | 14 | Thread thread = threadFactory.newThread(() -> { 15 | }); 16 | 17 | assertTrue(thread.isDaemon(), "Thread was no daemon"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/config/ConfigurationService.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.config; 2 | 3 | import lombok.Getter; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | @Getter 8 | public class ConfigurationService { 9 | 10 | private final ParsingConfig parsingConfig; 11 | private final DatabaseConfig databaseConfig; 12 | 13 | public ConfigurationService(ParsingConfig parsingConfig, DatabaseConfig databaseConfig) { 14 | this.parsingConfig = parsingConfig; 15 | this.databaseConfig = databaseConfig; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/submission/CompleteTask.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.submission; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | import org.immutables.gson.Gson; 6 | import org.immutables.serial.Serial; 7 | import org.immutables.value.Value; 8 | 9 | /** 10 | * A complete task that can be passed to the input of an Executor. 11 | */ 12 | @Serial.Structural 13 | @Gson.TypeAdapters 14 | @Value.Immutable 15 | public abstract class CompleteTask { 16 | 17 | public abstract List checks(); 18 | 19 | public abstract Submission submission(); 20 | 21 | public abstract UUID userIdentifier(); 22 | } 23 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/CommentParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.CommentIoMatcher; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 5 | 6 | /** 7 | * A parser for comments 8 | */ 9 | class CommentParser implements Parser { 10 | 11 | @Override 12 | public boolean canParse(String input) { 13 | return input.startsWith("#"); 14 | } 15 | 16 | @Override 17 | public InterleavedIoMatcher parse(String input) { 18 | return new CommentIoMatcher(input.substring(1)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/ErrorParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.ErrorIoMatcher; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 5 | 6 | /** 7 | * A {@link Parser} for an {@link ErrorIoMatcher}. 8 | */ 9 | class ErrorParser implements Parser { 10 | 11 | @Override 12 | public boolean canParse(String input) { 13 | return input.startsWith(" output); 17 | 18 | /** 19 | * Returns the error message. 20 | * 21 | * @return the error message 22 | */ 23 | String getError(); 24 | } 25 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/util/CheckedConsumer.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.util; 2 | 3 | /** 4 | * Represents an operation that accepts a single input argument, returns no result but could 5 | * possibly throw an exception. 6 | * 7 | * @param the type of the input to the operation 8 | * @param the type of the exception 9 | */ 10 | @FunctionalInterface 11 | public interface CheckedConsumer { 12 | 13 | /** 14 | * Performs this operation on the given argument. 15 | * 16 | * @param t the input argument 17 | * @throws E an exception if any occurs during the execution of the operation 18 | */ 19 | void accept(T t) throws E; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/crud/CrudTypes.ts: -------------------------------------------------------------------------------- 1 | export interface Identifiable { 2 | 3 | /** 4 | * The id of the object. 5 | */ 6 | id: any 7 | } 8 | 9 | export interface CrudRepository { 10 | 11 | /** 12 | * Updates an item. 13 | * 14 | * @param item the new item 15 | */ 16 | updateItem(item: A): Promise; 17 | 18 | /** 19 | * Deletes an item. 20 | * 21 | * @param id the id 22 | */ 23 | deleteItem(id: any): Promise; 24 | 25 | /** 26 | * Adds a new item. 27 | * 28 | * @param item the item 29 | */ 30 | addItem(item: A): Promise 31 | 32 | /** 33 | * Returns all items. 34 | */ 35 | fetchAll(): Promise> 36 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/main/java/me/ialistannen/simplecodetester/runner/execution/StreamsProcessOutput.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.runner.execution; 2 | 3 | import java.util.concurrent.Future; 4 | 5 | /** 6 | * A future that also provides snapshots of the current standard out and standard error of the 7 | * process it waits on. 8 | * 9 | * @param The result type returned by this Future's get method 10 | */ 11 | public interface StreamsProcessOutput extends Future { 12 | 13 | /** 14 | * @return a snapshot of the current standard output of the process 15 | */ 16 | String getCurrentStdOut(); 17 | 18 | /** 19 | * @return a snapshot of the current standard error 20 | */ 21 | String getCurrentStdErr(); 22 | } 23 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/VerbatimParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.VerbatimInputMatcher; 5 | 6 | /** 7 | * A parser for the {@link VerbatimInputMatcher}. 8 | */ 9 | class VerbatimParser implements Parser { 10 | 11 | @Override 12 | public boolean canParse(String input) { 13 | return !input.startsWith(">") && !input.startsWith("<"); 14 | } 15 | 16 | @Override 17 | public InterleavedIoMatcher parse(String input) { 18 | return new VerbatimInputMatcher(input); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/entities/CheckCategory.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.entities; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | import org.springframework.lang.NonNull; 11 | 12 | @Getter 13 | @Setter 14 | @RequiredArgsConstructor 15 | @AllArgsConstructor 16 | @ToString 17 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 18 | public class CheckCategory { 19 | 20 | @Setter(AccessLevel.NONE) 21 | @EqualsAndHashCode.Include 22 | private int id; 23 | @NonNull 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/exception/WebStatusExceptionMapperAdvice.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.exception; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.http.HttpServletResponse; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | 8 | @RestControllerAdvice 9 | public class WebStatusExceptionMapperAdvice { 10 | 11 | @ExceptionHandler(WebStatusCodeException.class) 12 | public void setStatusAndRemap(WebStatusCodeException exception, HttpServletResponse response) 13 | throws IOException { 14 | response.sendError(exception.getStatus().value(), exception.getMessage()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/config/ParsingConfig.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.config; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.Positive; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @EnableConfigurationProperties 13 | @ConfigurationProperties(prefix = "parsing") 14 | @Getter 15 | @Setter 16 | @Valid 17 | public class ParsingConfig { 18 | 19 | @Positive 20 | private int minCommands; 21 | } 22 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/RegexParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.RegularExpressionIoMatcher; 5 | 6 | /** 7 | * A {@link Parser} that creates a {@link RegularExpressionIoMatcher}. 8 | */ 9 | class RegexParser implements Parser { 10 | 11 | @Override 12 | public boolean canParse(String input) { 13 | return input.startsWith(" Jup, true", 13 | "<>, true", 14 | "hello, false", 15 | "", "#")) 21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/main/java/me/ialistannen/simplecodetester/runner/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.runner.util; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | 5 | /** 6 | * Helps with creating and handling threads. 7 | */ 8 | public final class ThreadUtil { 9 | 10 | 11 | /** 12 | * Wraps a {@link ThreadFactory} to only create {@link Thread#isDaemon() daemon} threads. 13 | * 14 | * @param original the original thread factory 15 | * @return a thread factory only creating daemon threads 16 | */ 17 | public static ThreadFactory daemonThreadFactory(ThreadFactory original) { 18 | return r -> { 19 | Thread thread = original.newThread(r); 20 | thread.setDaemon(true); 21 | return thread; 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/diana/TerminalFiles.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.diana; 2 | 3 | import edu.kit.informatik.Terminal; 4 | import java.nio.file.LinkOption; 5 | import java.nio.file.Path; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class TerminalFiles { 10 | 11 | public static String readString(Path input) { 12 | return String.join(System.lineSeparator(), Terminal.readFile(input.toString())); 13 | } 14 | 15 | public static List readAllLines(Path input) { 16 | return Arrays.asList(Terminal.readFile(input.toString())); 17 | } 18 | 19 | public static boolean exists(Path path, LinkOption... ignored) { 20 | return Terminal.hasFile(path.toString()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.config; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @EnableConfigurationProperties 14 | @ConfigurationProperties(prefix = "database") 15 | @Getter 16 | @Setter 17 | @Valid 18 | public class DatabaseConfig { 19 | 20 | @NotEmpty 21 | @NotNull 22 | private String url; 23 | } 24 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/config/RunnerConfig.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.config; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @EnableConfigurationProperties 14 | @ConfigurationProperties(prefix = "runner") 15 | @Getter 16 | @Setter 17 | @Valid 18 | public class RunnerConfig { 19 | 20 | @NotEmpty 21 | @NotNull 22 | private String password; 23 | } 24 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/compilation/Compiler.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.compilation; 2 | 3 | import me.ialistannen.simplecodetester.exceptions.CompilationException; 4 | import me.ialistannen.simplecodetester.submission.CompiledSubmission; 5 | import me.ialistannen.simplecodetester.submission.Submission; 6 | 7 | /** 8 | * A compiler that is able to compile a {@link Submission}. 9 | */ 10 | public interface Compiler { 11 | 12 | /** 13 | * Compiles the given {@link Submission}. 14 | * 15 | * @param submission the {@link Submission} to compile 16 | * @return the compiled submission 17 | * @throws CompilationException if an error occurs while compiling the submission 18 | */ 19 | CompiledSubmission compileSubmission(Submission submission); 20 | } 21 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/util/CheckedFunction.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.util; 2 | 3 | /** 4 | * Represents a function that accepts a single input argument, returns a result and could possibly 5 | * throw an exception. 6 | * 7 | * @param the type of the input to the function 8 | * @param the type of the function's return value 9 | * @param the type of the exception 10 | */ 11 | @FunctionalInterface 12 | public interface CheckedFunction { 13 | 14 | /** 15 | * Performs this operation on the given argument. 16 | * 17 | * @param t the input argument 18 | * @return R on successful evaulation 19 | * @throws E an exception if any occurs during the execution of the operation 20 | */ 21 | R accept(T t) throws E; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/diana/TerminalFileReader.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.diana; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.Reader; 6 | 7 | public class TerminalFileReader extends Reader { 8 | 9 | private final File file; 10 | 11 | public TerminalFileReader(File file) { 12 | this.file = file; 13 | } 14 | 15 | public TerminalFileReader(String file) { 16 | this(new File(file)); 17 | } 18 | 19 | public File getFile() { 20 | return file; 21 | } 22 | 23 | @Override 24 | public int read(char[] cbuf, int off, int len) throws IOException { 25 | throw new IllegalStateException("You shouldn't be calling this :( Rewriting failed!"); 26 | } 27 | 28 | @Override 29 | public void close() throws IOException { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/user/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from "vuex"; 2 | import { UserState, UserInfo } from "../../types"; 3 | 4 | export const mutations: MutationTree = { 5 | setRefreshToken(state: UserState, token: string) { 6 | state.refreshToken = token; 7 | }, 8 | setAccessToken(state: UserState, payload: UserInfo) { 9 | state.token = payload.token; 10 | state.userName = payload.username; 11 | state.displayName = payload.displayName; 12 | state.roles = payload.roles; 13 | }, 14 | logout(state: UserState) { 15 | state.userName = ""; 16 | state.roles = []; 17 | state.token = ""; 18 | state.refreshToken = ""; 19 | state.displayName = "Bobby Tables"; 20 | }, 21 | darkThemeSelected(state: UserState, selected: boolean) { 22 | state._darkThemeSelected = selected; 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /simplecodetester-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Simplecodetester 11 | 15 | 16 | 17 | 18 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | 8 | env: 9 | JAVA_VERSION: 17 10 | 11 | jobs: 12 | build-and-test-backend: 13 | name: "Build and test backend" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Set up JDK 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: ${{ env.JAVA_VERSION }} 20 | - uses: actions/checkout@v2 21 | - name: Run maven build and tests 22 | run: mvn clean package 23 | 24 | build-frontend: 25 | name: "Build frontend" 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/setup-node@v2-beta 29 | with: 30 | node-version: '12' 31 | - uses: actions/checkout@v2 32 | - name: Run frontend build 33 | run: cd simplecodetester-frontend && yarn install && yarn build 34 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/resources/db/migration/V1__Basic_Scherma.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE check_category 2 | ( 3 | id INTEGER PRIMARY KEY AUTOINCREMENT, 4 | name VARCHAR(255) 5 | ); 6 | 7 | CREATE TABLE code_check 8 | ( 9 | id INTEGER PRIMARY KEY AUTOINCREMENT, 10 | approved boolean NOT NULL, 11 | check_type VARCHAR(50), 12 | creation_time TIMESTAMP, 13 | name VARCHAR(255), 14 | text TEXT, 15 | update_time TIMESTAMP, 16 | category_id INTEGER, 17 | creator_id VARCHAR(255) 18 | ); 19 | 20 | CREATE TABLE user 21 | ( 22 | id VARCHAR(255) PRIMARY KEY NOT NULL, 23 | enabled BOOLEAN, 24 | name VARCHAR(255), 25 | password_hash VARCHAR(255) 26 | ); 27 | 28 | CREATE TABLE user_authorities 29 | ( 30 | user_id VARCHAR(255) NOT NULL, 31 | authorities VARCHAR(255) 32 | ); 33 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/CommentIoMatcher.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 4 | 5 | /** 6 | * A matcher for comments. 7 | */ 8 | public class CommentIoMatcher implements InterleavedIoMatcher { 9 | 10 | private String comment; 11 | 12 | /** 13 | * Creates a new comment matcher. 14 | * 15 | * @param comment the comment 16 | */ 17 | public CommentIoMatcher(String comment) { 18 | this.comment = comment; 19 | } 20 | 21 | @Override 22 | public boolean match(Block output) { 23 | return true; 24 | } 25 | 26 | @Override 27 | public String getError() { 28 | return null; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "#" + comment; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /simplecodetester-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env", "vuetify" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/resources/config/application.yml: -------------------------------------------------------------------------------- 1 | jwt.secret.key: "dsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsdsds" 2 | 3 | runner: 4 | password: "foobar" 5 | 6 | parsing: 7 | minCommands: 1 8 | 9 | management: 10 | endpoints: 11 | enabled-by-default: false 12 | web: 13 | exposure: 14 | include: '*' 15 | endpoint: 16 | metrics: 17 | enabled: true 18 | prometheus: 19 | enabled: true 20 | metrics: 21 | export: 22 | prometheus: 23 | enabled: true 24 | server: 25 | port: 8082 26 | 27 | cors: 28 | allowedOrigins: 29 | - "http://localhost:8080" 30 | - "https://localhost:8080" 31 | 32 | database: 33 | url: "jdbc:sqlite:./codetester.db" 34 | 35 | spring: 36 | mvc: 37 | converters: 38 | preferred-json-mapper: "gson" 39 | 40 | server: 41 | port: 8081 42 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/util/ResponseUtil.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.util; 2 | 3 | import java.util.Map; 4 | import lombok.experimental.UtilityClass; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | 8 | @UtilityClass 9 | public class ResponseUtil { 10 | 11 | /** 12 | * Returns an error {@link ResponseEntity} with the given status and message. 13 | * 14 | * @param status the {@link HttpStatus} code 15 | * @param message the message 16 | * @return the created response entity 17 | */ 18 | public static ResponseEntity error(HttpStatus status, String message) { 19 | @SuppressWarnings("unchecked") 20 | ResponseEntity error = (ResponseEntity) ResponseEntity.status(status) 21 | .body(Map.of("error", message)); 22 | return error; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/ErrorIoMatcher.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 4 | 5 | /** 6 | * A matcher matching error messages. 7 | */ 8 | public class ErrorIoMatcher implements InterleavedIoMatcher { 9 | 10 | private transient RegularExpressionIoMatcher matcher; 11 | 12 | /** 13 | * Creates a new error io matcher. 14 | */ 15 | public ErrorIoMatcher() { 16 | this.matcher = new RegularExpressionIoMatcher("Error, .*"); 17 | } 18 | 19 | @Override 20 | public boolean match(Block output) { 21 | return matcher.match(output); 22 | } 23 | 24 | @Override 25 | public String getError() { 26 | return "Expected an error message"; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return " = { 5 | setCategories(state: CheckCategoryState, payload: Array) { 6 | state.categories = payload; 7 | }, 8 | addCategory(state: CheckCategoryState, category: CheckCategory) { 9 | state.categories.push(category) 10 | }, 11 | removeCategory(state: CheckCategoryState, category: CheckCategory) { 12 | const index = state.categories.findIndex(it => it.id === category.id) 13 | if (index >= 0) { 14 | state.categories.splice(index, 1) 15 | } 16 | }, 17 | clear(state: CheckCategoryState) { 18 | state.categories = [] 19 | }, 20 | renameCategory(state: CheckCategoryState, { id, newName }) { 21 | state.categories.filter(it => it.id == id).forEach(it => it.name = newName) 22 | } 23 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/CheckFailedException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.LineResult; 7 | 8 | public class CheckFailedException extends RuntimeException { 9 | 10 | private final List outputLines; 11 | 12 | public CheckFailedException(String message) { 13 | super(message); 14 | this.outputLines = Collections.emptyList(); 15 | } 16 | 17 | public CheckFailedException(List outputLines) { 18 | this.outputLines = new ArrayList<>(outputLines); 19 | } 20 | 21 | /** 22 | * Returns the output lines. They contain a sequence of INPUT, OUTPUT and ERROR lines. 23 | * 24 | * @return the output lines 25 | */ 26 | public List getOutputLines() { 27 | return Collections.unmodifiableList(outputLines); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/LiteralIoMatcherTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class LiteralIoMatcherTest { 11 | 12 | @ParameterizedTest(name = "\"{1}\" matches \"{0}\" : {2}") 13 | @CsvSource({ 14 | "(Collections.singletonList(input))) 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/exceptions/CompilationException.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.exceptions; 2 | 3 | import java.util.Collections; 4 | import me.ialistannen.simplecodetester.compilation.CompilationOutput; 5 | import me.ialistannen.simplecodetester.compilation.ImmutableCompilationOutput; 6 | 7 | public class CompilationException extends RuntimeException { 8 | 9 | private final CompilationOutput output; 10 | 11 | public CompilationException(CompilationOutput output) { 12 | super("Compilation failed!"); 13 | this.output = output; 14 | } 15 | 16 | public CompilationException(String output) { 17 | this( 18 | ImmutableCompilationOutput.builder() 19 | .files(Collections.emptyList()) 20 | .successful(false) 21 | .diagnostics(Collections.emptyMap()) 22 | .output(output) 23 | .build() 24 | ); 25 | } 26 | 27 | public CompilationOutput getOutput() { 28 | return output; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/VerbatimInputMatcherTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class VerbatimInputMatcherTest { 11 | 12 | @ParameterizedTest(name = "\"{1}\" matches \"{0}\" : {2}") 13 | @CsvSource({ 14 | "(Collections.singletonList(input))) 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/submission/CompiledSubmission.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.submission; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import me.ialistannen.simplecodetester.compilation.CompilationOutput; 6 | import org.immutables.value.Value; 7 | 8 | /** 9 | * Returns a compiled version of a Submission. 10 | */ 11 | @Value.Immutable 12 | public abstract class CompiledSubmission { 13 | 14 | /** 15 | * Returns all compiled files in this submission. 16 | * 17 | * @return all compiled files in this submission 18 | */ 19 | public abstract List compiledFiles(); 20 | 21 | /** 22 | * Returns all generated auxiliary classes (inner classes, anon, ...) 23 | * 24 | * @return all generated auxiliary classes 25 | */ 26 | public abstract Map generatedAuxiliaryClasses(); 27 | 28 | /** 29 | * Returns the compilation output. 30 | * 31 | * @return the compilation output 32 | */ 33 | public abstract CompilationOutput compilationOutput(); 34 | } 35 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | SimpleCodeTester 7 | me.ialistannen 8 | 1.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | SimpleCodeTester-Lib 13 | 14 | 15 | 9.4 16 | 0.9.14 17 | 18 | 19 | 20 | 21 | org.ow2.asm 22 | asm 23 | ${asm.version} 24 | 25 | 26 | 27 | org.jooq 28 | joor 29 | ${joor.version} 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex, { StoreOptions } from 'vuex' 3 | import { RootState } from './types' 4 | import { user } from './modules/user' 5 | import { checkresult } from './modules/checkresult' 6 | import { checkcategory } from './modules/checkcategories' 7 | import { miscsettings } from './modules/miscsettings' 8 | import createPersistedState from 'vuex-persistedstate' 9 | 10 | const debug = process.env.NODE_ENV !== 'production' 11 | 12 | Vue.use(Vuex) 13 | 14 | const storeOptions: StoreOptions = { 15 | state: { 16 | baseUrl: 'http://localhost:8080' 17 | } as RootState, 18 | modules: { 19 | user, 20 | checkresult, 21 | checkcategory, 22 | miscsettings 23 | }, 24 | actions: { 25 | logout({ commit }) { 26 | commit("user/logout"); 27 | commit("checkresult/clear"); 28 | commit("checkcategory/clear") 29 | } 30 | }, 31 | strict: debug, 32 | plugins: [createPersistedState({ 33 | paths: ["user"] 34 | })] 35 | } 36 | 37 | export default new Vuex.Store(storeOptions); -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/UntrustedMain.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution; 2 | 3 | import com.google.gson.Gson; 4 | import java.io.InputStreamReader; 5 | import java.security.Policy; 6 | import me.ialistannen.simplecodetester.execution.security.ExecutorSandboxPolicy; 7 | import me.ialistannen.simplecodetester.submission.CompleteTask; 8 | import me.ialistannen.simplecodetester.util.ConfiguredGson; 9 | 10 | /** 11 | * The main class of the untrusted jvm. 12 | */ 13 | public final class UntrustedMain { 14 | 15 | private static CompleteTask readTask(Gson gson) { 16 | return gson.fromJson(new InputStreamReader(System.in), CompleteTask.class); 17 | } 18 | 19 | public static void main(String[] args) { 20 | Policy.setPolicy(new ExecutorSandboxPolicy()); 21 | System.setSecurityManager(new SecurityManager()); 22 | 23 | Gson gson = ConfiguredGson.createGson(); 24 | Executor executor = new Executor(System.out, gson); 25 | CompleteTask task = readTask(gson); 26 | executor.runTests(task); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/compilation/memory/InMemoryOutputObject.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.compilation.memory; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.OutputStream; 5 | import java.nio.file.Paths; 6 | import javax.tools.SimpleJavaFileObject; 7 | 8 | class InMemoryOutputObject extends SimpleJavaFileObject { 9 | 10 | private final ByteArrayOutputStream outputStream; 11 | private final String name; 12 | 13 | InMemoryOutputObject(String name) { 14 | // A path does necessarily exist 15 | super(Paths.get(name).toUri(), Kind.CLASS); 16 | this.name = name; 17 | 18 | this.outputStream = new ByteArrayOutputStream(); 19 | } 20 | 21 | @Override 22 | public OutputStream openOutputStream() { 23 | return outputStream; 24 | } 25 | 26 | @Override 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | /** 32 | * Returns the byte content of this file. 33 | * 34 | * @return the byte content 35 | */ 36 | byte[] getContent() { 37 | return outputStream.toByteArray(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/RegularExpressionIoMatcherTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class RegularExpressionIoMatcherTest { 11 | 12 | @ParameterizedTest(name = "\"{1}\" matches \"{1}\" : {2}") 13 | @CsvSource({ 14 | ".+, hello, true", 15 | "' ', ' ', true", 16 | "\\d+, test, false", 17 | "\\d+, 222323, true", 18 | "a+c, aaaaaaca, false", // anchored 19 | "a+ca, aaaaaaca, true" // anchored 20 | }) 21 | void testRegularMatcher(String matcherText, String input, boolean matches) { 22 | RegularExpressionIoMatcher matcher = new RegularExpressionIoMatcher(matcherText); 23 | 24 | assertEquals( 25 | matches, 26 | matcher.match(new Block<>(Collections.singletonList(input))) 27 | ); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/util/ErrorLogCapture.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import java.io.PrintStream; 4 | 5 | /** 6 | * Allows capturing {@link System#err}. 7 | */ 8 | public class ErrorLogCapture { 9 | 10 | private final StringOutputStream outputStream = new StringOutputStream(); 11 | private PrintStream originalError; 12 | 13 | /** 14 | * Starts capturing {@link System#err} output. 15 | */ 16 | public void startCapture() { 17 | originalError = System.err; 18 | System.setErr(new PrintStream(outputStream)); 19 | } 20 | 21 | /** 22 | * Stops capturing the {@link System#err} output. Can be called multiple times, it just has no 23 | * further effect in these cases. 24 | */ 25 | public void stopCapture() { 26 | if (originalError != null) { 27 | System.setErr(originalError); 28 | originalError = null; 29 | } 30 | } 31 | 32 | /** 33 | * Returns the captured {@link System#err} output. 34 | * 35 | * @return the captured output 36 | */ 37 | public String getCaptured() { 38 | return outputStream.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/CommentIoMatcherTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class CommentIoMatcherTest { 11 | 12 | @ParameterizedTest(name = "\"{0}\" is a comment: {1}") 13 | @CsvSource({ 14 | "Test, true", 15 | "Error, true", 16 | "Magic, true", 17 | "'<>Error,', true", 18 | "'<> Error,magic', true", 19 | "<> Error, true", 20 | "<>Error something, true", 21 | "'', true" 22 | }) 23 | void testErrorMatches(String input, boolean match) { 24 | String correctInput = input.replace("<>", "#"); 25 | 26 | CommentIoMatcher matcher = new CommentIoMatcher(correctInput); 27 | assertEquals( 28 | match, 29 | matcher.match(new Block<>(Collections.singletonList(correctInput))) 30 | ); 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/compilation/memory/InMemoryFileInputObject.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.compilation.memory; 2 | 3 | import java.nio.file.Paths; 4 | import javax.tools.SimpleJavaFileObject; 5 | 6 | class InMemoryFileInputObject extends SimpleJavaFileObject { 7 | 8 | private final String name; 9 | private final String content; 10 | 11 | /** 12 | * Construct a SimpleJavaFileObject providing input from a string. 13 | * 14 | * @param name the name of this file 15 | * @param content the content 16 | */ 17 | InMemoryFileInputObject(String name, String content) { 18 | super(Paths.get(name).toUri(), Kind.SOURCE); 19 | this.name = name; 20 | 21 | this.content = content; 22 | } 23 | 24 | @Override 25 | public CharSequence getCharContent(boolean ignoreEncodingErrors) { 26 | return content; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | /** 35 | * The content of this file. 36 | * 37 | * @return content of this file 38 | */ 39 | String getContent() { 40 | return content; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/SubmissionCheckResult.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Map; 6 | import me.ialistannen.simplecodetester.checks.CheckResult.ResultType; 7 | import me.ialistannen.simplecodetester.submission.Submission; 8 | import org.immutables.gson.Gson; 9 | import org.immutables.value.Value; 10 | 11 | /** 12 | * The result of checking a whole {@link Submission}. 13 | */ 14 | @Gson.TypeAdapters 15 | @Value.Immutable 16 | public abstract class SubmissionCheckResult { 17 | 18 | /** 19 | * Returns the {@link CheckResult}s for each file. 20 | * 21 | * @return the check results for each file 22 | */ 23 | public abstract Map> fileResults(); 24 | 25 | /** 26 | * Checks whether all tests succeeded. 27 | * 28 | * @return true if all tests succeeded 29 | */ 30 | public boolean overallSuccessful() { 31 | return fileResults().values().stream() 32 | .flatMap(Collection::stream) 33 | .allMatch(checkResult -> checkResult.result() != ResultType.FAILED); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import Router from 'vue-router' 5 | import store from './store' 6 | import Axios from 'axios'; 7 | import {isJwtValid} from './util/requests'; 8 | import vuetify from './plugins/vuetify'; 9 | 10 | Vue.config.productionTip = false 11 | 12 | Vue.use(Router) 13 | 14 | Axios.defaults.baseURL = process.env.VUE_APP_BASE_URL 15 | 16 | Axios.interceptors.request.use( 17 | async request => { 18 | if (!store.state.user.token) { 19 | return Promise.resolve(request); 20 | } 21 | 22 | if (!(request.url && request.url.indexOf("/login") >= 0) && !isJwtValid(store.state.user.token) && store.state.user.refreshToken) { 23 | await store.dispatch("user/fetchAccessToken"); 24 | } 25 | 26 | if (!request.headers) { 27 | request.headers = {} 28 | } 29 | 30 | // eslint-disable-next-line require-atomic-updates 31 | request.headers['Authorization'] = 'Bearer ' + store.state.user.token; 32 | return Promise.resolve(request) 33 | } 34 | ) 35 | 36 | new Vue({ 37 | router, 38 | store, 39 | vuetify, 40 | render: h => h(App), 41 | }).$mount('#app') 42 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/diana/TerminalBufferedReader.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.diana; 2 | 3 | import edu.kit.informatik.Terminal; 4 | import java.io.IOException; 5 | import java.io.Reader; 6 | import java.util.Arrays; 7 | import java.util.stream.Stream; 8 | 9 | public class TerminalBufferedReader implements AutoCloseable { 10 | 11 | private final String[] fileContent; 12 | private int fileContentOffset; 13 | 14 | public TerminalBufferedReader(Reader reader) { 15 | if (reader instanceof TerminalFileReader) { 16 | fileContent = Terminal.readFile(((TerminalFileReader) reader).getFile().toString()); 17 | } else { 18 | fileContent = null; 19 | } 20 | } 21 | 22 | public String readLine() throws IOException { 23 | if (fileContent == null) { 24 | return Terminal.readLine(); 25 | } 26 | if (fileContentOffset >= fileContent.length) { 27 | return null; 28 | } 29 | 30 | return fileContent[fileContentOffset++]; 31 | } 32 | 33 | public Stream lines() { 34 | return Arrays.stream(fileContent); 35 | } 36 | 37 | @Override 38 | public void close() { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/ErrorIoMatcherTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Collections; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.CsvSource; 10 | 11 | class ErrorIoMatcherTest { 12 | 13 | private ErrorIoMatcher matcher; 14 | 15 | @BeforeEach 16 | void setup() { 17 | matcher = new ErrorIoMatcher(); 18 | } 19 | 20 | @ParameterizedTest(name = "\"{0}\" is an error: {1}") 21 | @CsvSource({ 22 | "Test, false", 23 | "Error, false", 24 | "Magic, false", 25 | "'Error,', false", 26 | "'Error,magic', false", 27 | "'Error, ', true", 28 | "'Error, something', true", 29 | "'Error, true!', true", 30 | }) 31 | void testErrorMatches(String input, boolean match) { 32 | assertEquals( 33 | match, 34 | matcher.match(new Block<>(Collections.singletonList(input))) 35 | ); 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/user/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex'; 2 | import axios, { AxiosPromise } from 'axios'; 3 | import { UserState, UserLoginInfo, UserInfo } from '../../types'; 4 | import { RootState } from '../../types'; 5 | 6 | export const actions: ActionTree = { 7 | login({ commit, state, dispatch }, payload: UserLoginInfo): AxiosPromise { 8 | const data: FormData = new FormData(); 9 | data.append("username", payload.username); 10 | data.append("password", payload.password); 11 | 12 | return axios.post("/login", data).then((response) => { 13 | commit('setRefreshToken', response.data.token); 14 | 15 | return dispatch("fetchAccessToken") 16 | }); 17 | }, 18 | fetchAccessToken({ commit, state }): Promise { 19 | const formData = new FormData() 20 | formData.append("refreshToken", state.refreshToken!) 21 | 22 | return axios.post("/login/get-access-token", formData) 23 | .then(response => { 24 | commit("setAccessToken", new UserInfo( 25 | response.data.userName, 26 | response.data.token, 27 | response.data.displayName, 28 | response.data.roles 29 | )); 30 | }) 31 | } 32 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/security/JwtGenerator.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.security; 2 | 3 | import java.security.Key; 4 | import org.jose4j.jws.AlgorithmIdentifiers; 5 | import org.jose4j.jws.JsonWebSignature; 6 | import org.jose4j.jwt.JwtClaims; 7 | 8 | /** 9 | * A simple class that generates JWTs. 10 | */ 11 | public class JwtGenerator { 12 | 13 | private final Key key; 14 | 15 | /** 16 | * Creates a new JwtGenerator and initializes it with a {@link Key} to use. 17 | * 18 | * @param key the {@link Key} to use to sign JWTs 19 | */ 20 | public JwtGenerator(Key key) { 21 | this.key = key; 22 | } 23 | 24 | /** 25 | * Generates a new {@link JsonWebSignature} for the given {@link JwtClaims}. 26 | * 27 | * @param claims the claims 28 | * @return the generated {@link JsonWebSignature} 29 | */ 30 | public JsonWebSignature getSignature(JwtClaims claims) { 31 | JsonWebSignature jsonWebSignature = new JsonWebSignature(); 32 | jsonWebSignature.setKey(key); 33 | jsonWebSignature.setPayload(claims.toJson()); 34 | jsonWebSignature.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); 35 | return jsonWebSignature; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/CheckCategorySelection.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/util/StringOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class StringOutputStreamTest { 11 | 12 | private StringOutputStream outputStream; 13 | 14 | @BeforeEach 15 | void setup() { 16 | outputStream = new StringOutputStream(); 17 | } 18 | 19 | @Test 20 | void canRetrieveData() throws IOException { 21 | outputStream.write("Hello!".getBytes(StandardCharsets.UTF_8)); 22 | 23 | assertEquals( 24 | outputStream.toString(), 25 | "Hello!" 26 | ); 27 | } 28 | 29 | @Test 30 | void preservesNewlines() throws IOException { 31 | outputStream.write("Hello\n!".getBytes(StandardCharsets.UTF_8)); 32 | 33 | assertEquals( 34 | outputStream.toString(), 35 | "Hello\n!" 36 | ); 37 | } 38 | 39 | @Test 40 | void canHandleUnicode() throws IOException { 41 | outputStream.write("Hello λψ".getBytes(StandardCharsets.UTF_8)); 42 | 43 | assertEquals( 44 | outputStream.toString(), 45 | "Hello λψ" 46 | ); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/result/Result.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.result; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | import me.ialistannen.simplecodetester.checks.CheckResult; 7 | import me.ialistannen.simplecodetester.compilation.CompilationOutput; 8 | import org.immutables.gson.Gson; 9 | import org.immutables.serial.Serial; 10 | import org.immutables.value.Value; 11 | 12 | /** 13 | * The result of testing a submission. 14 | */ 15 | @Serial.Structural 16 | @Gson.TypeAdapters 17 | @Value.Immutable 18 | public abstract class Result { 19 | 20 | public abstract Map> fileResults(); 21 | 22 | public abstract Optional compilationOutput(); 23 | 24 | public abstract Optional timeoutData(); 25 | 26 | public abstract Optional crashData(); 27 | 28 | @Serial.Structural 29 | @Value.Immutable 30 | @Gson.TypeAdapters 31 | public static abstract class TimeoutData { 32 | 33 | public abstract String lastTest(); 34 | } 35 | 36 | @Serial.Structural 37 | @Value.Immutable 38 | @Gson.TypeAdapters 39 | public static abstract class CrashData { 40 | 41 | public abstract String lastTest(); 42 | 43 | public abstract String additionalContext(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/diana/TerminalScanner.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.diana; 2 | 3 | import edu.kit.informatik.Terminal; 4 | import java.io.File; 5 | import java.io.InputStream; 6 | import me.ialistannen.simplecodetester.exceptions.ReadMoreLinesThanProvidedException; 7 | 8 | public class TerminalScanner implements AutoCloseable { 9 | 10 | private final String[] fileContent; 11 | private int fileContentOffset; 12 | 13 | public TerminalScanner(File file) { 14 | fileContent = Terminal.readFile(file.getName()); 15 | } 16 | 17 | public TerminalScanner(@SuppressWarnings("unused") InputStream ignored) { 18 | fileContent = null; 19 | } 20 | 21 | public boolean hasNext() { 22 | if (fileContent == null) { 23 | return true; 24 | } 25 | return fileContentOffset < fileContent.length; 26 | } 27 | 28 | public boolean hasNextLine() { 29 | return hasNext(); 30 | } 31 | 32 | public String nextLine() { 33 | if (fileContent == null) { 34 | return Terminal.readLine(); 35 | } 36 | if (fileContentOffset >= fileContent.length) { 37 | throw new ReadMoreLinesThanProvidedException(); 38 | } 39 | 40 | return fileContent[fileContentOffset++]; 41 | } 42 | 43 | @Override 44 | public void close() {} 45 | } 46 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/security/ExecutorSandboxPolicy.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.security; 2 | 3 | import java.security.AllPermission; 4 | import java.security.Permission; 5 | import java.security.PermissionCollection; 6 | import java.security.Permissions; 7 | import java.security.Policy; 8 | import java.security.ProtectionDomain; 9 | import me.ialistannen.simplecodetester.execution.running.SubmissionClassLoader; 10 | 11 | public class ExecutorSandboxPolicy extends Policy { 12 | 13 | private static final PermissionCollection ELEVATED_PERMISSIONS = getElevatedPermissions(); 14 | 15 | @Override 16 | public PermissionCollection getPermissions(ProtectionDomain domain) { 17 | if (domain.getClassLoader() instanceof SubmissionClassLoader) { 18 | return domain.getPermissions(); 19 | } 20 | return ELEVATED_PERMISSIONS; 21 | } 22 | 23 | @Override 24 | public boolean implies(ProtectionDomain domain, Permission permission) { 25 | return getPermissions(domain).implies(permission); 26 | } 27 | 28 | private static PermissionCollection getElevatedPermissions() { 29 | Permissions permissions = new Permissions(); 30 | permissions.add(new AllPermission()); 31 | permissions.setReadOnly(); 32 | return permissions; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/compilation/CompilationOutput.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.compilation; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Map; 6 | import me.ialistannen.simplecodetester.submission.CompiledFile; 7 | import org.immutables.gson.Gson; 8 | import org.immutables.value.Value; 9 | 10 | /** 11 | * The compiler output for a whole collection of files. 12 | */ 13 | @Gson.TypeAdapters 14 | @Value.Immutable 15 | public abstract class CompilationOutput { 16 | 17 | /** 18 | * Returns diagnostic warnings for each class. 19 | * 20 | * @return the diagnostic warnings for the files 21 | */ 22 | public abstract Map> diagnostics(); 23 | 24 | /** 25 | * Checks whether the compilation was successful. 26 | * 27 | * @return whether the compilation was successful 28 | */ 29 | public abstract boolean successful(); 30 | 31 | /** 32 | * Returns any compiler output. 33 | * 34 | * @return the compiler output or an empty string if none 35 | */ 36 | public abstract String output(); 37 | 38 | /** 39 | * Returns a list with all compiled files. 40 | * 41 | * @return all compiled files. Empty if an error occurred. 42 | */ 43 | public abstract Collection files(); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "vuex"; 2 | import { RootState, UserState } from "../../types"; 3 | import { mutations } from "./mutations"; 4 | import { actions } from "./actions"; 5 | import { isJwtValid } from "@/util/requests"; 6 | 7 | export const state: UserState = { 8 | userName: "Unknown", 9 | roles: [], 10 | displayName: "Bobby Tables", 11 | token: null, 12 | refreshToken: null, 13 | _darkThemeSelected: undefined, 14 | isTokenValid(): boolean { 15 | return isJwtValid(this.refreshToken); 16 | }, 17 | isAdmin: function() { 18 | return this.roles.includes("ROLE_ADMIN"); 19 | }, 20 | isEditor: function() { 21 | return this.roles.includes("ROLE_EDITOR"); 22 | }, 23 | darkThemeSelected: function() { 24 | if (this._darkThemeSelected !== undefined) { 25 | return this._darkThemeSelected; 26 | } 27 | return this.browserPrefersDarkTheme(); 28 | }, 29 | browserPrefersDarkTheme: function() { 30 | return window.matchMedia("(prefers-color-scheme: dark)").matches; 31 | }, 32 | usesBrowsersThemePreferences: function() { 33 | return this._darkThemeSelected === undefined; 34 | }, 35 | }; 36 | 37 | const namespaced: boolean = true; 38 | 39 | export const user: Module = { 40 | namespaced, 41 | state, 42 | mutations, 43 | actions, 44 | }; 45 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/util/StringOutputStream.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | /** 9 | * A simple {@link OutputStream} that collects the data in a string. 10 | */ 11 | public class StringOutputStream extends OutputStream { 12 | 13 | private final ByteArrayOutputStream byteArrayOutputStream; 14 | 15 | public StringOutputStream() { 16 | this.byteArrayOutputStream = new ByteArrayOutputStream(); 17 | } 18 | 19 | @Override 20 | public synchronized void write(int b) { 21 | byteArrayOutputStream.write(b); 22 | } 23 | 24 | @Override 25 | public synchronized void write(byte[] b) throws IOException { 26 | byteArrayOutputStream.write(b); 27 | } 28 | 29 | @Override 30 | public synchronized void write(byte[] b, int off, int len) { 31 | byteArrayOutputStream.write(b, off, len); 32 | } 33 | 34 | /** 35 | * Returns the underlying read string. 36 | * 37 | * @return the underlying string 38 | */ 39 | public synchronized String getString() { 40 | return byteArrayOutputStream.toString(StandardCharsets.UTF_8); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return getString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/edu/kit/kastel/trafficsimulation/io/SimulationFileLoader.java: -------------------------------------------------------------------------------- 1 | package edu.kit.kastel.trafficsimulation.io; 2 | 3 | import edu.kit.informatik.Terminal; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class SimulationFileLoader { 10 | 11 | private final Path prefix; 12 | 13 | public SimulationFileLoader(String prefix) throws IOException { 14 | this.prefix = Path.of(prefix).normalize(); 15 | } 16 | 17 | public List loadStreets() throws IOException { 18 | try { 19 | return Arrays.asList(Terminal.readFile(prefix.resolve("streets.sim").toString())); 20 | } catch (IllegalArgumentException e) { 21 | throw new IOException(e.getMessage()); 22 | } 23 | } 24 | 25 | public List loadCrossings() throws IOException { 26 | try { 27 | return Arrays.asList(Terminal.readFile(prefix.resolve("crossings.sim").toString())); 28 | } catch (IllegalArgumentException e) { 29 | throw new IOException(e.getMessage()); 30 | } 31 | } 32 | 33 | public List loadCars() throws IOException { 34 | try { 35 | return Arrays.asList(Terminal.readFile(prefix.resolve("cars.sim").toString())); 36 | } catch (IllegalArgumentException e) { 37 | throw new IOException(e.getMessage()); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/Main.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend; 2 | 3 | import com.google.gson.Gson; 4 | import me.ialistannen.simplecodetester.backend.db.storage.DatabaseStorage; 5 | import me.ialistannen.simplecodetester.backend.services.config.ConfigurationService; 6 | import me.ialistannen.simplecodetester.util.ConfiguredGson; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; 10 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 12 | import org.springframework.context.annotation.Bean; 13 | 14 | @SpringBootApplication(exclude = { 15 | FlywayAutoConfiguration.class, 16 | HibernateJpaAutoConfiguration.class, 17 | DataSourceAutoConfiguration.class 18 | }) 19 | public class Main { 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(Main.class, args); 23 | } 24 | 25 | @Bean 26 | public DatabaseStorage provideDatabaseStorage(ConfigurationService config) { 27 | return new DatabaseStorage(config.getDatabaseConfig().getUrl()); 28 | } 29 | 30 | @Bean 31 | public Gson provideGson() { 32 | return ConfiguredGson.createGson(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/util/ClassParsingUtil.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import java.util.Optional; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | import lombok.experimental.UtilityClass; 7 | 8 | @UtilityClass 9 | public class ClassParsingUtil { 10 | 11 | private static final Pattern CLASS_DECLARATION_PATTERN = Pattern 12 | .compile("(class|interface|@interface|enum) (\\w+) ?"); 13 | private static final Pattern PACKAGE_PATTERN = Pattern.compile("package (\\S+);"); 14 | 15 | /** 16 | * Returns the name of the class. 17 | * 18 | * @param input the input to check 19 | * @return the class name if found 20 | */ 21 | public static Optional getClassName(String input) { 22 | Matcher matcher = CLASS_DECLARATION_PATTERN.matcher(input); 23 | if (!matcher.find()) { 24 | return Optional.empty(); 25 | } 26 | return Optional.ofNullable(matcher.group(2)); 27 | } 28 | 29 | /** 30 | * Returns the package of the class. 31 | * 32 | * @param input the input to check 33 | * @return the package if found 34 | */ 35 | public static Optional getPackage(String input) { 36 | Matcher matcher = PACKAGE_PATTERN.matcher(input); 37 | if (!matcher.find()) { 38 | return Optional.empty(); 39 | } 40 | return Optional.ofNullable(matcher.group(1)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/RegexParserTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.RegularExpressionIoMatcher; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class RegexParserTest { 11 | 12 | private RegexParser regexParser; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | regexParser = new RegexParser(); 17 | } 18 | 19 | @ParameterizedTest(name = "\"{0}\" is a literal: {1}") 20 | @CsvSource({ 21 | "< output) { 24 | return literal.equals(output.next()); 25 | } 26 | 27 | @Override 28 | public String getError() { 29 | return "Expected '" + literal + "'"; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) { 35 | return true; 36 | } 37 | if (o == null || getClass() != o.getClass()) { 38 | return false; 39 | } 40 | LiteralIoMatcher that = (LiteralIoMatcher) o; 41 | return Objects.equals(literal, that.literal); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(literal); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return " output) { 24 | return output.next().equals(content); 25 | } 26 | 27 | @Override 28 | public String getError() { 29 | return "Expected '" + content + "'"; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) { 35 | return true; 36 | } 37 | if (o == null || getClass() != o.getClass()) { 38 | return false; 39 | } 40 | VerbatimInputMatcher that = (VerbatimInputMatcher) o; 41 | return Objects.equals(content, that.content); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(content); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return content; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/entities/User.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.entities; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import javax.persistence.ElementCollection; 8 | import javax.persistence.FetchType; 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | import lombok.ToString; 12 | 13 | @ToString 14 | @Getter 15 | @Setter 16 | public class User { 17 | 18 | private String id; 19 | private String name; 20 | @Expose(serialize = false) 21 | private String passwordHash; 22 | private boolean enabled; 23 | 24 | @ElementCollection(fetch = FetchType.EAGER) 25 | private List authorities; 26 | 27 | public User(String id, String name, String passwordHash, boolean enabled, 28 | List authorities) { 29 | this.id = id; 30 | this.name = name; 31 | this.passwordHash = passwordHash; 32 | this.enabled = enabled; 33 | this.authorities = new ArrayList<>(authorities); 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || getClass() != o.getClass()) { 42 | return false; 43 | } 44 | User user = (User) o; 45 | return Objects.equals(id, user.id); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/edu/kit/informatik/TerminalTest.java: -------------------------------------------------------------------------------- 1 | package edu.kit.informatik; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.List; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class TerminalTest { 10 | 11 | @BeforeEach 12 | void setUp() { 13 | Terminal.reset(); 14 | } 15 | 16 | @Test 17 | void multilineWorks() { 18 | String text = "\nHello\nYou\n"; 19 | Terminal.printLine(text); 20 | 21 | assertEquals( 22 | List.of("", "Hello", "You", ""), 23 | Terminal.getOutputLines().get(0) 24 | ); 25 | } 26 | 27 | @Test 28 | void firstOutputIsBeforeRead() { 29 | String text = "Hello"; 30 | Terminal.printLine(text); 31 | 32 | assertEquals( 33 | List.of(List.of("Hello")), 34 | Terminal.getOutputLines() 35 | ); 36 | } 37 | 38 | @Test 39 | void separatesByInput() { 40 | Terminal.setInput(List.of("", "")); 41 | List> input = List.of( 42 | List.of("Hello\nYou"), 43 | List.of("How are you?") 44 | ); 45 | 46 | for (List list : input) { 47 | list.forEach(Terminal::printLine); 48 | Terminal.readLine(); 49 | } 50 | 51 | assertEquals( 52 | List.of( 53 | List.of("Hello", "You"), 54 | List.of("How are you?"), 55 | List.of() 56 | ), 57 | Terminal.getOutputLines() 58 | ); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/matcher/RegularExpressionIoMatcher.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.matcher; 2 | 3 | import java.util.Objects; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.Block; 5 | 6 | /** 7 | * Matches a line using a regular expression. 8 | */ 9 | public class RegularExpressionIoMatcher implements InterleavedIoMatcher { 10 | 11 | private String pattern; 12 | 13 | /** 14 | * The pattern to match against. 15 | * 16 | * @param pattern the pattern to match 17 | */ 18 | public RegularExpressionIoMatcher(String pattern) { 19 | this.pattern = pattern; 20 | } 21 | 22 | @Override 23 | public boolean match(Block output) { 24 | return output.next().matches(pattern); 25 | } 26 | 27 | @Override 28 | public String getError() { 29 | return "Expected a match for pattern '" + pattern + "'"; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) { 35 | return true; 36 | } 37 | if (o == null || getClass() != o.getClass()) { 38 | return false; 39 | } 40 | RegularExpressionIoMatcher that = (RegularExpressionIoMatcher) o; 41 | return Objects.equals(pattern, that.pattern); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(pattern); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return " claims['exp'] 46 | } 47 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify, { VLayout } from "vuetify/lib"; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | icons: { 8 | iconfont: "mdiSvg", 9 | }, 10 | components: { 11 | VLayout, 12 | }, 13 | theme: { 14 | options: { 15 | customProperties: true, 16 | }, 17 | dark: false, 18 | themes: { 19 | light: { 20 | primary: "#4CAF50", 21 | accent: "#E040FB", 22 | red: "#FF6347", 23 | line_input: "#4169E1", // royalblue 24 | line_parameter: "#808080", // gray 25 | line_output: "#455a64", 26 | line_error: "#FF6347", // tomato 27 | line_other: "#008000", // green 28 | line_prefix: "#999999", // lightgray 29 | highlighted_button_color: "#4169e1", 30 | check_background: "#eeeeee", 31 | code_border: "#E040FB", 32 | muted: "#848484" 33 | }, 34 | dark: { 35 | error: "#c84a4b", 36 | primary: "#4CAF50", 37 | secondary: "#c51162", 38 | accent: "#f8f32b", 39 | line_input: "#81f499", 40 | line_parameter: "#808080", // geay 41 | line_output: "#3da35d", 42 | line_error: "#FF6347", // tomato 43 | line_other: "#5da0e4", 44 | line_prefix: "#7c8c8f", 45 | highlighted_button_color: "#e980fc", 46 | check_background: "#303030", 47 | code_border: "#808080", 48 | muted: "#7c8c8f" 49 | }, 50 | }, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/util/MainClassDetectionUtil.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Modifier; 5 | import java.util.function.Function; 6 | import java.util.function.Predicate; 7 | import lombok.experimental.UtilityClass; 8 | 9 | /** 10 | * A small util class for finding main methods. 11 | */ 12 | @UtilityClass 13 | public class MainClassDetectionUtil { 14 | 15 | /** 16 | * Returns a predicate matching whether a class has a main class. 17 | * 18 | * @param extractor the extractor to convert the given object to a class 19 | * @param the type of the object 20 | * @return a predicate checking {@link #hasMainMethod(Class)} 21 | */ 22 | public static Predicate hasMain(Function> extractor) { 23 | return t -> hasMainMethod(extractor.apply(t)); 24 | } 25 | 26 | /** 27 | * Checks if the given class has a JVM main method. 28 | * 29 | * @param target the class to check 30 | * @return true if the given class has a main method 31 | */ 32 | public static boolean hasMainMethod(Class target) { 33 | Method mainMethod; 34 | try { 35 | mainMethod = target.getMethod("main", String[].class); 36 | } catch (NoSuchMethodException e) { 37 | return false; 38 | } 39 | 40 | return mainMethod.getReturnType() == void.class 41 | && Modifier.isStatic(mainMethod.getModifiers()) 42 | && Modifier.isPublic(mainMethod.getModifiers()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/parsing/VerbatimParserTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io.parsing; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.VerbatimInputMatcher; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class VerbatimParserTest { 11 | 12 | private VerbatimParser verbatimParser; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | verbatimParser = new VerbatimParser(); 17 | } 18 | 19 | @ParameterizedTest(name = "\"{0}\" can be read verbatim: {1}") 20 | @CsvSource({ 21 | "< cool stuff, false", 27 | "cool stuff, true" 28 | }) 29 | void canParseVerbatim(String input, boolean canParse) { 30 | assertEquals( 31 | canParse, 32 | verbatimParser.canParse(input) 33 | ); 34 | } 35 | 36 | @ParameterizedTest(name = "Parsing \"{0}\"") 37 | @CsvSource({ 38 | "Cool stuff", 39 | "Is here", 40 | "magic with spaces", 41 | "and with \" some special", 42 | "'characters in there\t\nHey" 43 | }) 44 | void parsesToCorrectVerbatim(String input) { 45 | assertEquals( 46 | new VerbatimInputMatcher(input), 47 | verbatimParser.parse(input) 48 | ); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | SimpleCodeTester 7 | me.ialistannen 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | SimpleCodeTester-Runner 13 | 14 | 15 | 17 16 | 17 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-shade-plugin 24 | 25 | 26 | 28 | 29 | me.ialistannen.simplecodetester.runner.Main 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | me.ialistannen 41 | SimpleCodeTester-Lib 42 | 1.0-SNAPSHOT 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/ThemeSelector.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | 40 | 55 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/storage/DBWriteAccess.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.storage; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.locks.Lock; 5 | import org.jooq.DSLContext; 6 | import org.jooq.DeleteWhereStep; 7 | import org.jooq.InsertSetStep; 8 | import org.jooq.Record; 9 | import org.jooq.Table; 10 | import org.jooq.UpdateSetFirstStep; 11 | 12 | /** 13 | * Allows read and write access to a database. 14 | */ 15 | public class DBWriteAccess extends DBReadAccess { 16 | 17 | private final Lock lock; 18 | private boolean closed = false; 19 | 20 | public DBWriteAccess(DSLContext ctx, Lock lock) { 21 | super(ctx); 22 | this.lock = Objects.requireNonNull(lock); 23 | this.lock.lock(); 24 | } 25 | 26 | @Override 27 | public void close() { 28 | try { 29 | super.close(); 30 | } finally { 31 | this.closed = true; 32 | this.lock.unlock(); 33 | } 34 | } 35 | 36 | private void checkNotClosed() { 37 | if (this.closed) { 38 | throw new IllegalStateException("write access already closed"); 39 | } 40 | } 41 | 42 | public DeleteWhereStep deleteFrom(Table table) { 43 | checkNotClosed(); 44 | return ctx.deleteFrom(table); 45 | } 46 | 47 | public UpdateSetFirstStep update(Table table) { 48 | checkNotClosed(); 49 | return ctx.update(table); 50 | } 51 | 52 | public InsertSetStep insertInto(Table table) { 53 | checkNotClosed(); 54 | return ctx.insertInto(table); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/checksubmit/CheckSubmitErrorDialog.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 43 | 44 | 54 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/checks/parsers/StaticInputOutputCheckParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints.checks.parsers; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonSyntaxException; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import me.ialistannen.simplecodetester.backend.exception.CheckParseException; 8 | import me.ialistannen.simplecodetester.checks.defaults.StaticInputOutputCheck; 9 | 10 | /** 11 | * A parser for a {@link StaticInputOutputCheck}. 12 | */ 13 | public class StaticInputOutputCheckParser implements CheckParser { 14 | 15 | private Gson gson; 16 | 17 | /** 18 | * Creates a new static input output parser. 19 | * 20 | * @param gson the gson to use 21 | */ 22 | public StaticInputOutputCheckParser(Gson gson) { 23 | this.gson = gson; 24 | } 25 | 26 | @Override 27 | public StaticInputOutputCheck parse(String payload) { 28 | try { 29 | ResponseBase response = gson.fromJson(payload, ResponseBase.class); 30 | 31 | List input = Arrays.asList(response.data.input.split("\n")); 32 | 33 | return new StaticInputOutputCheck(input, response.data.output, response.name); 34 | } catch (JsonSyntaxException e) { 35 | throw new CheckParseException("Error parsing check: " + e.getMessage()); 36 | } 37 | } 38 | 39 | private static class ResponseBase { 40 | 41 | String name; 42 | 43 | CheckRepresentation data; 44 | } 45 | 46 | private static class CheckRepresentation { 47 | 48 | String input; 49 | String output; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/diana/Rewriter.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.diana; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.commons.ClassRemapper; 6 | import org.objectweb.asm.commons.Remapper; 7 | 8 | public class Rewriter extends Remapper { 9 | 10 | public byte[] remap(byte[] classBytes, ClassLoader classLoader) { 11 | ClassReader reader = new ClassReader(classBytes); 12 | ClassWriter writer = new ClassWriter(0) { 13 | @Override 14 | protected ClassLoader getClassLoader() { 15 | return classLoader; 16 | } 17 | }; 18 | reader.accept( 19 | new ClassRemapper(writer, new Mapper()), 20 | 0 21 | ); 22 | 23 | return writer.toByteArray(); 24 | } 25 | 26 | private static class Mapper extends Remapper { 27 | 28 | @Override 29 | public String map(String internalName) { 30 | if (internalName.equals("java/util/Scanner")) { 31 | return "me/ialistannen/simplecodetester/execution/diana/TerminalScanner"; 32 | } 33 | if (internalName.equals("java/io/BufferedReader")) { 34 | return "me/ialistannen/simplecodetester/execution/diana/TerminalBufferedReader"; 35 | } 36 | if (internalName.equals("java/io/FileReader")) { 37 | return "me/ialistannen/simplecodetester/execution/diana/TerminalFileReader"; 38 | } 39 | if (internalName.equals("java/nio/file/Files")) { 40 | return "me/ialistannen/simplecodetester/execution/diana/TerminalFiles"; 41 | } 42 | return super.map(internalName); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/store/modules/checkcategories/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex'; 2 | import axios, { AxiosPromise } from 'axios'; 3 | import { CheckCategory, CheckCategoryState } from '../../types'; 4 | import { RootState } from '../../types'; 5 | 6 | export const actions: ActionTree = { 7 | async fetchAll({ commit, state }): Promise> { 8 | if (state.categories.length > 0) { 9 | return state.categories; 10 | } 11 | const response = await axios.get("/check-category/get-all"); 12 | const data = (response.data as Array).map(it => new CheckCategory(it.name, it.id)); 13 | commit("setCategories", data); 14 | return data; 15 | }, 16 | async addNew({ commit }, checkName: string) { 17 | const formData = new FormData() 18 | formData.append("name", checkName) 19 | const response = await axios.post("/check-category/add-new", formData); 20 | const category = new CheckCategory(response.data.name, response.data.id); 21 | 22 | commit("addCategory", category) 23 | 24 | return category 25 | }, 26 | async deleteCheckCategory({ commit }, category: CheckCategory) { 27 | const response = await axios.delete(`/check-category/delete/${category.id}`) 28 | 29 | commit("removeCategory", category) 30 | 31 | return response 32 | }, 33 | async renameCheckCategory({ commit }, { id, newName }) { 34 | const formData = new FormData() 35 | formData.append("id", id) 36 | formData.append("newName", newName) 37 | 38 | const response = await axios.patch('/check-category/rename', formData) 39 | 40 | commit("renameCategory", { id: id, newName: newName }) 41 | 42 | return response 43 | } 44 | }; -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/LineResult.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * A single line in the output. 7 | */ 8 | public class LineResult { 9 | 10 | private final Type type; 11 | private final String content; 12 | 13 | /** 14 | * Creates a new line result. 15 | * 16 | * @param type the type of the line 17 | * @param content the content of the line 18 | */ 19 | public LineResult(Type type, String content) { 20 | this.type = type; 21 | this.content = content; 22 | } 23 | 24 | /** 25 | * The type of the line. 26 | * 27 | * @return the type of the line 28 | */ 29 | public Type getType() { 30 | return type; 31 | } 32 | 33 | /** 34 | * The content of the line. 35 | * 36 | * @return the content of the line 37 | */ 38 | public String getContent() { 39 | return content; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) { 45 | return true; 46 | } 47 | if (o == null || getClass() != o.getClass()) { 48 | return false; 49 | } 50 | LineResult that = (LineResult) o; 51 | return type == that.type && 52 | Objects.equals(content, that.content); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(type, content); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return String.format("[%-7s] %s", type, content); 63 | } 64 | 65 | /** 66 | * The type of the line. 67 | */ 68 | public enum Type { 69 | PARAMETER, 70 | ERROR, 71 | INPUT, 72 | OUTPUT, 73 | OTHER 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/users/ChangeOwnPassword.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 52 | 53 | 55 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/storage/DBReadAccess.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.storage; 2 | 3 | import java.io.Closeable; 4 | import java.util.Objects; 5 | import org.jooq.Condition; 6 | import org.jooq.DSLContext; 7 | import org.jooq.Record; 8 | import org.jooq.Record1; 9 | import org.jooq.Record10; 10 | import org.jooq.Record11; 11 | import org.jooq.Record12; 12 | import org.jooq.Record13; 13 | import org.jooq.Record14; 14 | import org.jooq.Record15; 15 | import org.jooq.Record16; 16 | import org.jooq.Record17; 17 | import org.jooq.Record18; 18 | import org.jooq.Record19; 19 | import org.jooq.Record2; 20 | import org.jooq.Record20; 21 | import org.jooq.Record21; 22 | import org.jooq.Record22; 23 | import org.jooq.Record3; 24 | import org.jooq.Record4; 25 | import org.jooq.Record5; 26 | import org.jooq.Record6; 27 | import org.jooq.Record7; 28 | import org.jooq.Record8; 29 | import org.jooq.Record9; 30 | import org.jooq.Result; 31 | import org.jooq.Select; 32 | import org.jooq.SelectField; 33 | import org.jooq.SelectSelectStep; 34 | import org.jooq.SelectWhereStep; 35 | import org.jooq.Table; 36 | 37 | /** 38 | * Allows read only access to a database. 39 | */ 40 | public class DBReadAccess implements Closeable { 41 | 42 | protected final DSLContext ctx; 43 | 44 | public DBReadAccess(DSLContext ctx) { 45 | this.ctx = Objects.requireNonNull(ctx); 46 | } 47 | 48 | @Override 49 | public void close() { 50 | } 51 | 52 | public DSLContext dsl() { 53 | return ctx; 54 | } 55 | 56 | public boolean fetchExists(Select query) { 57 | return ctx.fetchExists(query); 58 | } 59 | 60 | public SelectWhereStep selectFrom(Table table) { 61 | return ctx.selectFrom(table); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/src/main/java/me/ialistannen/simplecodetester/execution/compilation/memory/ClassFileManager.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.execution.compilation.memory; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import javax.tools.FileObject; 6 | import javax.tools.ForwardingJavaFileManager; 7 | import javax.tools.JavaFileObject; 8 | import javax.tools.JavaFileObject.Kind; 9 | import javax.tools.StandardJavaFileManager; 10 | 11 | /** 12 | * A manager for compiled class files. 13 | */ 14 | class ClassFileManager extends ForwardingJavaFileManager { 15 | 16 | private final Map outputFileMap; 17 | 18 | /** 19 | * Creates a new instance of ForwardingJavaFileManager. 20 | * 21 | * @param fileManager delegate to this file manager 22 | */ 23 | ClassFileManager(StandardJavaFileManager fileManager) { 24 | super(fileManager); 25 | 26 | this.outputFileMap = new HashMap<>(); 27 | } 28 | 29 | @Override 30 | public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, 31 | FileObject sibling) { 32 | InMemoryOutputObject outputObject = new InMemoryOutputObject(className); 33 | 34 | outputFileMap.put(className, outputObject); 35 | 36 | return outputObject; 37 | } 38 | 39 | InMemoryOutputObject getForClassPath(String path) { 40 | return outputFileMap.get(sanitizeToClassName(path)); 41 | } 42 | 43 | String sanitizeToClassName(String path) { 44 | return path 45 | .replace(".java", "") 46 | .replace("/", "."); 47 | } 48 | 49 | /** 50 | * Returns all written output objects. 51 | * 52 | * @return all written output objects 53 | */ 54 | Map getAll() { 55 | return outputFileMap; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/sandbox/start-container.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | if [ "$#" -ne 1 ]; then 5 | echo "Usage: $0 " 6 | fi 7 | 8 | # --rm 9 | # Delete the container on exit 10 | # -i 11 | # Keep stdin open as we transfer data through it 12 | # --init 13 | # Add dockers init process to reap zombies and relay signals 14 | # --cap-drop 15 | # Drop every capability 16 | # --network 17 | # Deny network access 18 | # --pids-limit 19 | # Limit generated PIDs to 2000. This limits the container to 2000 processes in total! 20 | # This should be enough to render fork bombs useless 21 | # --memory 22 | # Limit the memory consumption 23 | # --read-only 24 | # The user does not need to create any file in the image 25 | # --name 26 | # Give the container a name with a unique prefix so we can prevent the script from 27 | # affecting other containers. 28 | # name 29 | # Name of image 30 | docker run \ 31 | --rm \ 32 | -i \ 33 | --init \ 34 | --cap-drop=ALL \ 35 | --network=none \ 36 | --pids-limit=2000 \ 37 | --memory=200M \ 38 | --read-only \ 39 | --name "codetester-$1" \ 40 | codetester-sandbox 41 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/checks/parsers/InterleavedIoCheckParser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints.checks.parsers; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonSyntaxException; 5 | import me.ialistannen.simplecodetester.backend.exception.CheckParseException; 6 | import me.ialistannen.simplecodetester.backend.services.config.ParsingConfig; 7 | import me.ialistannen.simplecodetester.checks.defaults.io.InterleavedStaticIOCheck; 8 | import me.ialistannen.simplecodetester.checks.defaults.io.parsing.InterleavedIoParser; 9 | 10 | /** 11 | * A parser for an {@link InterleavedStaticIOCheck}. 12 | */ 13 | public class InterleavedIoCheckParser implements CheckParser { 14 | 15 | private final InterleavedIoParser interleavedIoParser; 16 | private final Gson gson; 17 | 18 | /** 19 | * Creates a new InterleavedIoCheckParser. 20 | * 21 | * @param gson the gson instance to use 22 | * @param parsingConfig the parsing config 23 | */ 24 | public InterleavedIoCheckParser(Gson gson, ParsingConfig parsingConfig) { 25 | this.gson = gson; 26 | this.interleavedIoParser = new InterleavedIoParser( 27 | parsingConfig.getMinCommands() 28 | ); 29 | } 30 | 31 | @Override 32 | public InterleavedStaticIOCheck parse(String payload) { 33 | try { 34 | ResponseBase responseBase = gson.fromJson(payload, ResponseBase.class); 35 | 36 | return interleavedIoParser.fromString(responseBase.data.input, responseBase.name); 37 | } catch (JsonSyntaxException | IllegalArgumentException e) { 38 | throw new CheckParseException("Error parsing check: " + e.getMessage()); 39 | } 40 | } 41 | 42 | private static class ResponseBase { 43 | 44 | String name; 45 | CheckRepresentation data; 46 | } 47 | 48 | private static class CheckRepresentation { 49 | 50 | String input; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/BlockTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | class BlockTest { 14 | 15 | private Block block; 16 | 17 | @BeforeEach 18 | void seup() { 19 | block = new Block<>(List.of("Hello", "world", "!")); 20 | } 21 | 22 | @Test 23 | void nextAndHasNext() { 24 | assertTrue(block.hasNext()); 25 | assertEquals(block.next(), "Hello"); 26 | 27 | assertTrue(block.hasNext()); 28 | assertEquals(block.next(), "world"); 29 | 30 | assertTrue(block.hasNext()); 31 | assertEquals(block.next(), "!"); 32 | 33 | assertFalse(block.hasNext()); 34 | } 35 | 36 | @Test 37 | void callingNextInInvalidStateThrowsException() { 38 | Block block = new Block<>(Collections.emptyList()); 39 | 40 | assertThrows( 41 | IndexOutOfBoundsException.class, 42 | block::next 43 | ); 44 | } 45 | 46 | @Test 47 | void copyDoesNotStartFromOldCursorPosition() { 48 | block.next(); 49 | block.next(); 50 | 51 | assertEquals( 52 | block.copyFromStart().next(), 53 | "Hello" 54 | ); 55 | } 56 | 57 | @Test 58 | void isAtStartWhenNotAtStart() { 59 | block.next(); 60 | 61 | assertFalse(block.isAtStart()); 62 | } 63 | 64 | @Test 65 | void isAtStartWhenAtStart() { 66 | assertTrue(block.isAtStart()); 67 | } 68 | 69 | @Test 70 | void copyIsAtStart() { 71 | block.next(); 72 | assertTrue(block.copyFromStart().isAtStart()); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/submission/CompiledFile.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.submission; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Supplier; 5 | import me.ialistannen.simplecodetester.exceptions.CompiledClassNotLoadableException; 6 | import org.immutables.gson.Gson; 7 | import org.immutables.value.Value; 8 | 9 | /** 10 | * A submitted file that was successfully compiled. 11 | */ 12 | @Gson.TypeAdapters 13 | @Value.Immutable 14 | public abstract class CompiledFile { 15 | 16 | /** 17 | * Returns the bytes of this file's ".class" file. 18 | * 19 | * @return the bytes of this file's ".class" file 20 | */ 21 | public abstract byte[] classFile(); 22 | 23 | /** 24 | * Returns the file source content. 25 | * 26 | * @return the file source content 27 | */ 28 | public abstract String content(); 29 | 30 | /** 31 | * Returns the fully qualified name of this class. 32 | * 33 | * @return the qualified name of this class 34 | */ 35 | public abstract String qualifiedName(); 36 | 37 | /** 38 | * Supplies the classloader that should be used to load this class, if any. 39 | * 40 | * @return classloader that should be used to load this class, if any 41 | * @see #asClass() 42 | */ 43 | @Gson.Ignore 44 | public abstract Optional> classLoaderSupplier(); 45 | 46 | /** 47 | * Returns this CompiledFile as a java class. 48 | * 49 | * @return the loaded java class for this CompiledFile 50 | * @throws CompiledClassNotLoadableException if the class was not found for some reason 51 | */ 52 | public Class asClass() { 53 | try { 54 | ClassLoader classLoader = classLoaderSupplier().orElseThrow().get(); 55 | 56 | return Class.forName(qualifiedName(), true, classLoader); 57 | } catch (ClassNotFoundException e) { 58 | throw new CompiledClassNotLoadableException(qualifiedName(), e); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/main/java/me/ialistannen/simplecodetester/runner/config/RunnerConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.runner.config; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.StreamSupport; 10 | 11 | public class RunnerConfiguration { 12 | 13 | private final List startContainerCommand; 14 | private final List killContainerCommand; 15 | private final int maxRuntimeSeconds; 16 | private final String backendUrl; 17 | private final String password; 18 | 19 | public RunnerConfiguration(JsonObject jsonObject) { 20 | this.startContainerCommand = fromArray(jsonObject.getAsJsonArray("container_start_command")); 21 | this.killContainerCommand = fromArray(jsonObject.getAsJsonArray("container_kill_command")); 22 | this.maxRuntimeSeconds = jsonObject.getAsJsonPrimitive("max_runtime_seconds").getAsInt(); 23 | this.password = jsonObject.getAsJsonPrimitive("backend_password").getAsString(); 24 | this.backendUrl = jsonObject.getAsJsonPrimitive("backend_url").getAsString(); 25 | } 26 | 27 | public List getStartContainerCommand() { 28 | return Collections.unmodifiableList(startContainerCommand); 29 | } 30 | 31 | public List getKillContainerCommand() { 32 | return Collections.unmodifiableList(killContainerCommand); 33 | } 34 | 35 | public int getMaxRuntimeSeconds() { 36 | return maxRuntimeSeconds; 37 | } 38 | 39 | public String getBackendUrl() { 40 | return backendUrl; 41 | } 42 | 43 | public String getPassword() { 44 | return password; 45 | } 46 | 47 | private static List fromArray(JsonArray array) { 48 | return StreamSupport.stream(array.spliterator(), false) 49 | .map(JsonElement::getAsString) 50 | .collect(Collectors.toList()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SimpleCodeTester-Executor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | SimpleCodeTester 7 | me.ialistannen 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | SimpleCodeTester-Executor 13 | 14 | 15 | 17 16 | 17 17 | 9.4 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-shade-plugin 25 | 26 | 27 | 29 | 30 | me.ialistannen.simplecodetester.execution.UntrustedMain 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | me.ialistannen 42 | SimpleCodeTester-Lib 43 | 1.0-SNAPSHOT 44 | 45 | 46 | 47 | org.ow2.asm 48 | asm 49 | ${asm.version} 50 | 51 | 52 | org.ow2.asm 53 | asm-commons 54 | ${asm.version} 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/checks/CheckCategoryService.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.checks; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import me.ialistannen.simplecodetester.backend.db.entities.CheckCategory; 6 | import me.ialistannen.simplecodetester.backend.db.repos.CheckCategoryRepository; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class CheckCategoryService { 11 | 12 | private final CheckCategoryRepository repository; 13 | 14 | public CheckCategoryService(CheckCategoryRepository repository) { 15 | this.repository = repository; 16 | } 17 | 18 | /** 19 | * Returns all {@link CheckCategory CheckCategories}. 20 | * 21 | * @return all check categories 22 | */ 23 | public List getAll() { 24 | return repository.findAll(); 25 | } 26 | 27 | /** 28 | * Returns a {@link CheckCategory} by its id. 29 | * 30 | * @param id the id 31 | * @return the {@link CheckCategory} with that id 32 | */ 33 | public Optional getById(int id) { 34 | return repository.findById(id); 35 | } 36 | 37 | /** 38 | * Adds a new {@link CheckCategory}. 39 | * 40 | * @param name the name of the category 41 | * @return the created category with its id field populated 42 | */ 43 | public CheckCategory addCategory(String name) { 44 | return repository.save(name); 45 | } 46 | 47 | /** 48 | * Renames a category. 49 | * 50 | * @param id the id of the category 51 | * @param newName the new name for it 52 | * @return the renamed category 53 | */ 54 | public Optional rename(int id, String newName) { 55 | return repository.rename(id, newName); 56 | } 57 | 58 | /** 59 | * Deletes a {@link CheckCategory}. 60 | * 61 | * @param id the if of the category 62 | * @return true if the category existed 63 | */ 64 | public boolean removeCategory(int id) { 65 | return repository.deleteById(id) != 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/checks/defaults/io/ResultBlockTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import me.ialistannen.simplecodetester.checks.defaults.io.LineResult.Type; 9 | import org.junit.jupiter.api.RepeatedTest; 10 | import org.junit.jupiter.api.Test; 11 | 12 | class ResultBlockTest { 13 | 14 | @RepeatedTest(10) 15 | void addRandomLines() { 16 | ResultBlock block = new ResultBlock(); 17 | 18 | List lines = getRandomLines(); 19 | for (LineResult line : lines) { 20 | switch (line.getType()) { 21 | case ERROR: 22 | block.addError(line.getContent()); 23 | break; 24 | case OUTPUT: 25 | block.addOutput(line.getContent()); 26 | break; 27 | case INPUT: 28 | block.addInput(line.getContent()); 29 | break; 30 | case PARAMETER: 31 | block.addParameter(line.getContent()); 32 | break; 33 | } 34 | } 35 | 36 | assertEquals( 37 | lines, 38 | block.getResults() 39 | ); 40 | } 41 | 42 | private List getRandomLines() { 43 | List line = new ArrayList<>(); 44 | 45 | for (int i = 0; i < 50; i++) { 46 | int random = ThreadLocalRandom.current().nextInt(3); 47 | line.add(new LineResult( 48 | Type.values()[random], "Something " + ThreadLocalRandom.current().nextInt() 49 | )); 50 | } 51 | 52 | return line; 53 | } 54 | 55 | @Test 56 | void addBlock() { 57 | ResultBlock block = new ResultBlock(); 58 | block.addInput("Hello"); 59 | 60 | ResultBlock other = new ResultBlock(); 61 | other.addInput("world"); 62 | 63 | block.add(other); 64 | 65 | assertEquals( 66 | List.of(new LineResult(Type.INPUT, "Hello"), new LineResult(Type.INPUT, "world")), 67 | block.getResults() 68 | ); 69 | } 70 | } -------------------------------------------------------------------------------- /simplecodetester-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplecodetester-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@mdi/js": "^6.5.95", 12 | "axios": "^0.24.0", 13 | "global": "^4.4.0", 14 | "vue": "^2.5.17", 15 | "vue-class-component": "^7.1.0", 16 | "vue-prism-editor": "^1.3.0", 17 | "vue-property-decorator": "^9.1.2", 18 | "vue-router": "^3.5.3", 19 | "vuetify": "^2.6.2", 20 | "vuex": "^3.0.1", 21 | "vuex-persistedstate": "^4.1.0" 22 | }, 23 | "devDependencies": { 24 | "@typescript-eslint/eslint-plugin": "^5.8.1", 25 | "@typescript-eslint/parser": "^5.8.1", 26 | "@vue/cli-plugin-babel": "^4.5.15", 27 | "@vue/cli-plugin-eslint": "^4.5.15", 28 | "@vue/cli-plugin-typescript": "^4.5.15", 29 | "@vue/cli-service": "^4.5.15", 30 | "@vue/eslint-config-typescript": "^10.0.0", 31 | "babel-eslint": "^10.0.1", 32 | "css-loader": "^6.5.1", 33 | "deepmerge": "^4.0.0", 34 | "eslint": "^7.28.0", 35 | "eslint-plugin-vue": "^8.2.0", 36 | "sass": "~1.32.13", 37 | "sass-loader": "^10.1.0", 38 | "style-loader": "^3.3.1", 39 | "stylus": "^0.56.0", 40 | "stylus-loader": "^6.2.0", 41 | "typescript": "^4.5.4", 42 | "vue-cli-plugin-vuetify": "^2.4.5", 43 | "vue-template-compiler": "^2.5.17", 44 | "vuetify-loader": "^1.7.3", 45 | "webpack-bundle-analyzer": "^4.5.0" 46 | }, 47 | "eslintConfig": { 48 | "root": true, 49 | "env": { 50 | "node": true 51 | }, 52 | "extends": [ 53 | "plugin:vue/essential", 54 | "eslint:recommended", 55 | "@vue/typescript" 56 | ], 57 | "rules": {}, 58 | "parserOptions": { 59 | "parser": "@typescript-eslint/parser" 60 | }, 61 | "plugins": [ 62 | "@typescript-eslint" 63 | ] 64 | }, 65 | "postcss": { 66 | "plugins": { 67 | "autoprefixer": {} 68 | } 69 | }, 70 | "browserslist": [ 71 | "> 1%", 72 | "last 2 versions", 73 | "not ie <= 8" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

SimpleCodeTester

4 |
5 | 6 | The final exam in some courses is a programming exercise where students create 7 | their own programs. Many people are just starting out with programming (and we 8 | are only human), so *actually testing the code* is extremely paramount. 9 | 10 | Sadly, most traditional tools, like JUnit, are hard to use for newcomers, do not 11 | allow easy sharing of test cases and are time consuming to write. 12 | **SimpleCodeTester** is a website that solves this problem: 13 | Everybody can create checks verifying program output in a simple and intuitive 14 | format. The editor for this supports syntax highlighting to make it a bit more 15 | enjoyable :) 16 | 17 | Finally, users can test their code against all submitted tests. 18 | Everybody is free to participate and create tests - everybody will benefit from 19 | them instant they are submitted. 20 | 21 | Great care was taken to ensure the online runtime environment is secure (it is 22 | run with a restrictive security manager and each submission is sandboxed in its 23 | own Docker container, without any capabilities or network/disk access). We also 24 | conciously decided, that no user submissions should ever be stored - your code 25 | is received, passed to the sandbox via stdin, fully compiled in-memory and the 26 | last traces of it die with the result response. Feel free to have a look at how 27 | it is implemented though! 28 | 29 | # Screenshots 30 | The following is a submitted check 31 | ![Sample check](media/sample-check-light.png) 32 | 33 | ---- 34 | 35 | And we also have a dark theme! 36 | ![Sample check dark theme](media/sample-check-dark.png) 37 | 38 | ---- 39 | 40 | If you submit your code, it will automatically be tested against all checks. 41 | Here is our demo test from earlier, all grown up and failing! In such a case, 42 | we try to match the output your program produced with the expected output and 43 | provide useful error messages. 44 | ![Check result](media/check-result-light.png) 45 | 46 | ---- 47 | 48 | The logo was artfully crafted by 49 | [@libaurea](https://github.com/libaurea/codetester-logo) :tada: 50 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/users/ChangePassword.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 80 | 81 | 82 | 84 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/ResultBlock.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import me.ialistannen.simplecodetester.checks.defaults.io.LineResult.Type; 7 | 8 | /** 9 | * A block storing results. 10 | */ 11 | class ResultBlock { 12 | 13 | private List results; 14 | 15 | /** 16 | * Creates a new results blocks. 17 | */ 18 | ResultBlock() { 19 | this.results = new ArrayList<>(); 20 | } 21 | 22 | /** 23 | * Adds everything from the given result block to this. 24 | * 25 | * @param other the result block to add the results from 26 | */ 27 | void add(ResultBlock other) { 28 | results.addAll(other.getResults()); 29 | } 30 | 31 | /** 32 | * Adds the given line. 33 | * 34 | * @param result the result to add 35 | */ 36 | void add(LineResult result) { 37 | results.add(result); 38 | } 39 | 40 | /** 41 | * Adds an output line. 42 | * 43 | * @param output the output to add 44 | */ 45 | void addOutput(String output) { 46 | results.add(new LineResult(Type.OUTPUT, output)); 47 | } 48 | 49 | /** 50 | * Adds a line of undefined type. 51 | * 52 | * @param line the line to add 53 | */ 54 | void addOther(String line) { 55 | results.add(new LineResult(Type.OTHER, line)); 56 | } 57 | 58 | /** 59 | * Adds an input line 60 | * 61 | * @param input the input 62 | */ 63 | void addInput(String input) { 64 | results.add(new LineResult(Type.INPUT, input)); 65 | } 66 | /** 67 | * Adds a parameter line. 68 | * 69 | * @param parameterValue the parameter to add 70 | */ 71 | void addParameter(String parameterValue) { 72 | results.add(new LineResult(Type.PARAMETER, parameterValue)); 73 | } 74 | 75 | /** 76 | * Adds an error line 77 | * 78 | * @param error the error 79 | */ 80 | void addError(String error) { 81 | results.add(new LineResult(Type.ERROR, error)); 82 | } 83 | 84 | /** 85 | * Returns all results. 86 | * 87 | * @return the results 88 | */ 89 | List getResults() { 90 | return Collections.unmodifiableList(results); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 60 | 61 | 81 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/security/AuthenticatedJwtUser.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.security; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | import lombok.ToString; 7 | import org.jose4j.jwt.JwtClaims; 8 | import org.jose4j.jwt.MalformedClaimException; 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | 13 | /** 14 | * A user that is authenticated by a JWT: 15 | */ 16 | @ToString 17 | public class AuthenticatedJwtUser implements UserDetails { 18 | 19 | private final JwtClaims claims; 20 | private final String principal; 21 | private final List authorities; 22 | private final boolean enabled; 23 | 24 | /** 25 | * Creates a new AuthenticatedJwtUser from the given {@link JwtClaims}. 26 | * 27 | * @param claims the claims to build from 28 | * @throws MalformedClaimException if the claims were malformed 29 | */ 30 | AuthenticatedJwtUser(JwtClaims claims) throws MalformedClaimException { 31 | this.claims = claims; 32 | this.principal = claims.getSubject(); 33 | this.authorities = claims.getStringListClaimValue("roles").stream() 34 | .map(SimpleGrantedAuthority::new) 35 | .collect(Collectors.toList()); 36 | this.enabled = (boolean) claims.getClaimValue("enabled"); 37 | } 38 | 39 | @Override 40 | public Collection getAuthorities() { 41 | return authorities; 42 | } 43 | 44 | @Override 45 | public String getPassword() { 46 | return null; 47 | } 48 | 49 | @Override 50 | public String getUsername() { 51 | return principal; 52 | } 53 | 54 | @Override 55 | public boolean isAccountNonExpired() { 56 | // JWT was valid and the auth is stateless 57 | return true; 58 | } 59 | 60 | @Override 61 | public boolean isAccountNonLocked() { 62 | // JWT was valid and the auth is stateless 63 | return true; 64 | } 65 | 66 | @Override 67 | public boolean isCredentialsNonExpired() { 68 | // JWT was valid and the auth is stateless 69 | return true; 70 | } 71 | 72 | @Override 73 | public boolean isEnabled() { 74 | return enabled; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /SimpleCodeTester-Runner/src/main/java/me/ialistannen/simplecodetester/runner/execution/WaitingFutureTask.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.runner.execution; 2 | 3 | import java.util.concurrent.CancellationException; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.FutureTask; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.TimeoutException; 9 | import me.ialistannen.simplecodetester.util.StringOutputStream; 10 | 11 | /** 12 | * A future task that waits for the underlying worker to die before returning from get. 13 | * 14 | * @param the type of the task 15 | */ 16 | class WaitingFutureTask implements Future, StreamsProcessOutput { 17 | 18 | private final Thread worker; 19 | private final FutureTask underlying; 20 | private final StringOutputStream stdErr; 21 | private final StringOutputStream stdOut; 22 | 23 | public WaitingFutureTask(FutureTask task, StringOutputStream stdOut, 24 | StringOutputStream stdErr) { 25 | this.underlying = task; 26 | this.stdOut = stdOut; 27 | this.stdErr = stdErr; 28 | this.worker = new Thread(underlying, "WaitingFutureTask worker"); 29 | this.worker.start(); 30 | } 31 | 32 | @Override 33 | public boolean cancel(boolean mayInterruptIfRunning) { 34 | return underlying.cancel(mayInterruptIfRunning); 35 | } 36 | 37 | @Override 38 | public boolean isCancelled() { 39 | return underlying.isCancelled(); 40 | } 41 | 42 | @Override 43 | public boolean isDone() { 44 | return underlying.isDone(); 45 | } 46 | 47 | @Override 48 | public T get() throws InterruptedException, ExecutionException { 49 | if (isCancelled()) { 50 | worker.join(); 51 | } 52 | return underlying.get(); 53 | } 54 | 55 | @Override 56 | public T get(long timeout, TimeUnit unit) 57 | throws InterruptedException, ExecutionException, TimeoutException { 58 | if (isCancelled()) { 59 | worker.join(unit.toMillis(timeout)); 60 | throw new CancellationException("Execution cancelled, thread died"); 61 | } 62 | return underlying.get(timeout, unit); 63 | } 64 | 65 | @Override 66 | public String getCurrentStdOut() { 67 | return stdOut.getString(); 68 | } 69 | 70 | @Override 71 | public String getCurrentStdErr() { 72 | return stdErr.getString(); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/util/ClassParsingUtilTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.CsvSource; 7 | 8 | class ClassParsingUtilTest { 9 | 10 | @ParameterizedTest(name = "\"{0}\" should be {1}.") 11 | @CsvSource({ 12 | "class Test, Test", 13 | "public class Test, Test", 14 | "interface Test, Test", 15 | "public interface Test, Test", 16 | "enum Test, Test", 17 | "public enum Test, Test", 18 | "@interface Test, Test", 19 | "public @interface Test, Test" 20 | }) 21 | void retrieveName(String input, String className) { 22 | assertEquals( 23 | className, 24 | ClassParsingUtil.getClassName(input).get() 25 | ); 26 | } 27 | 28 | @ParameterizedTest(name = "\"{0}\" should be {1}.") 29 | @CsvSource({ 30 | "/*\\n * Hello\\n */\\nclass Test, Test", 31 | "/*\\n * Hello\\n */\\npublic class Test, Test", 32 | "/*\\n * Hello\\n */\\ninterface Test, Test", 33 | "/*\\n * Hello\\n */\\npublic interface Test, Test", 34 | "/*\\n * Hello\\n */\\nenum Test, Test", 35 | "/*\\n * Hello\\n */\\npublic enum Test, Test", 36 | "/*\\n * Hello\\n */\\n@interface Test, Test", 37 | "/*\\n * Hello\\n */\\npublic @interface Test, Test" 38 | }) 39 | void retrieveNameWithJavadoc(String input, String className) { 40 | assertEquals( 41 | className, 42 | ClassParsingUtil.getClassName(input).get() 43 | ); 44 | } 45 | 46 | @ParameterizedTest(name = "\"{0}\" should be {1}.") 47 | @CsvSource({ 48 | "package me.ialistannen;\\nclass Test, me.ialistannen", 49 | "package me.ialistannen;\\npublic class Test, me.ialistannen", 50 | "package me.ialistannen;\\ninterface Test, me.ialistannen", 51 | "package me.ialistannen;\\npublic interface Test, me.ialistannen", 52 | "package me.ialistannen;\\nenum Test, me.ialistannen", 53 | "package me.ialistannen;\\npublic enum Test, me.ialistannen", 54 | "package me.ialistannen;\\n@interface Test, me.ialistannen", 55 | "package me.ialistannen;\\npublic @interface Test, me.ialistannen" 56 | }) 57 | void retrievePackage(String input, String packageName) { 58 | assertEquals( 59 | packageName, 60 | ClassParsingUtil.getPackage(input).get() 61 | ); 62 | } 63 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/checks/parsers/CheckParsers.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints.checks.parsers; 2 | 3 | import com.google.gson.Gson; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import me.ialistannen.simplecodetester.backend.exception.CheckParseException; 7 | import me.ialistannen.simplecodetester.backend.services.config.ParsingConfig; 8 | import me.ialistannen.simplecodetester.checks.Check; 9 | import me.ialistannen.simplecodetester.checks.defaults.StaticInputOutputCheck; 10 | import me.ialistannen.simplecodetester.checks.defaults.io.InterleavedStaticIOCheck; 11 | 12 | /** 13 | * Contains parsers that parse some input to a proper check. 14 | */ 15 | public class CheckParsers { 16 | 17 | private Map> parsers; 18 | 19 | /** 20 | * Creates a new collection of check parsers. 21 | * 22 | * @param gson the gson instance to use 23 | * @param parsingConfig the parser config 24 | */ 25 | public CheckParsers(Gson gson, ParsingConfig parsingConfig) { 26 | this.parsers = new HashMap<>(); 27 | 28 | addParser(InterleavedStaticIOCheck.class, new InterleavedIoCheckParser(gson, parsingConfig)); 29 | addParser(StaticInputOutputCheck.class, new StaticInputOutputCheckParser(gson)); 30 | } 31 | 32 | /** 33 | * Adds a new parser. 34 | * 35 | * @param checkClass the class of the check 36 | * @param parser the check parser 37 | * @param the type of the check to parse 38 | */ 39 | public void addParser(Class checkClass, CheckParser parser) { 40 | parsers.put(checkClass.getSimpleName(), parser); 41 | } 42 | 43 | /** 44 | * Parses a payload to a check. 45 | * 46 | * @param payload the payload to parse 47 | * @param checkKeyword the keyword for the check 48 | * @param the type of the check 49 | * @return the parsed check 50 | * @throws me.ialistannen.simplecodetester.backend.exception.CheckParseException if parsing 51 | * fails 52 | */ 53 | public T parsePayload(String payload, String checkKeyword) { 54 | // actually safe, as we store it correctl 55 | @SuppressWarnings("unchecked") 56 | CheckParser checkParser = (CheckParser) parsers.get(checkKeyword); 57 | if (checkParser == null) { 58 | throw new CheckParseException("No parser found for " + checkKeyword); 59 | } 60 | return checkParser.parse(payload); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/Block.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.List; 5 | import java.util.Queue; 6 | 7 | /** 8 | * A grouped collection of some type that allows iteration. 9 | * 10 | * @param th type of the collection 11 | */ 12 | public class Block { 13 | 14 | private final List data; 15 | private transient int position; 16 | private transient Queue markedPositions; 17 | 18 | /** 19 | * Creates a new Block with the given data. 20 | * 21 | * @param data the data 22 | */ 23 | public Block(List data) { 24 | this.data = data; 25 | this.markedPositions = new ArrayDeque<>(); 26 | } 27 | 28 | /** 29 | * Returns whether there is any data left in the block. 30 | * 31 | * @return true if there is any data left in the block 32 | */ 33 | public boolean hasNext() { 34 | return position < data.size(); 35 | } 36 | 37 | /** 38 | * Returns the next element and advances this block. 39 | * 40 | * @return the next element 41 | * @throws IndexOutOfBoundsException if {@link #hasNext()} is not true 42 | */ 43 | public T next() { 44 | T output = data.get(position); 45 | 46 | if (hasNext()) { 47 | position++; 48 | } 49 | return output; 50 | } 51 | 52 | /** 53 | * Returns whether this block is at the start of its elements. 54 | * 55 | * @return true if this block is at the start of its elements 56 | */ 57 | boolean isAtStart() { 58 | return position == 0; 59 | } 60 | 61 | /** 62 | * Creates a shallow copy of this block that will start iterating at the first element again. 63 | * 64 | * @return a shallow copy 65 | */ 66 | Block copyFromStart() { 67 | return new Block<>(data); 68 | } 69 | 70 | /** 71 | * Marks the position so later calls to 72 | */ 73 | void mark() { 74 | markedPositions.offer(position); 75 | } 76 | 77 | /** 78 | * Returns all data since the last mark, removing the mark in the process. 79 | * 80 | * @return all data since the last mark 81 | */ 82 | List getFromLastMark() { 83 | int oldPosition = markedPositions.remove(); 84 | 85 | return data.subList(oldPosition, position); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return "Block{" + 91 | "data=" + data + 92 | ", position=" + position + 93 | '}'; 94 | } 95 | } -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/users/UserModificationComponent.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 75 | 76 | 78 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/upload/MultiFileSelect.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 76 | 77 | 99 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/util/Highlighting.ts: -------------------------------------------------------------------------------- 1 | import { IoLine, IoLineType } from "@/store/types"; 2 | 3 | /** 4 | * Guesses the type of a line. 5 | * 6 | * @param line the line to guess the type for 7 | */ 8 | export function guessLineType(line: string): IoLineType { 9 | if (line.startsWith("#")) { 10 | return IoLineType.OTHER; 11 | } 12 | if (line.startsWith("> ")) { 13 | return IoLineType.INPUT; 14 | } 15 | if (line.startsWith("$$ ")) { 16 | return IoLineType.PARAMETER; 17 | } 18 | 19 | if (line.startsWith(" { 32 | const result: string[] = []; 33 | 34 | // Dirty, but overwrite the type here. This is not really an error line, just an error output matcher. 35 | // This means the backend sets the type to "OUTPUT", but for displaying it is nicer to work with "ERROR" 36 | let type: IoLineType = line.lineType; 37 | if (line.content === " 0) { 42 | result.push(`${escapeHtml(getPrefix(line))}`); 43 | } 44 | result.push( 45 | `${escapeHtml(getRest(line) || "\n")}` 46 | ); 47 | 48 | const inner = result.join(""); 49 | 50 | const classes = ["line", "line-" + type].join(" "); 51 | return `${inner}`; 52 | }); 53 | return resultSpans.join(""); 54 | } 55 | 56 | /** 57 | * Escapes all HTML in a given string. 58 | * 59 | * @param input the input string to escape 60 | */ 61 | function escapeHtml(input: string): string { 62 | const p = document.createElement("p"); 63 | p.appendChild(document.createTextNode(input)); 64 | return p.innerHTML; 65 | } 66 | 67 | function getPrefix(line: IoLine) { 68 | const regex = /^(> |<.|# |\$\$ )/; 69 | const match = regex.exec(line.content); 70 | if (match) { 71 | return match[1]; 72 | } 73 | 74 | return ""; 75 | } 76 | 77 | function getRest(line: IoLine) { 78 | let content: string; 79 | if (getPrefix(line) !== "") { 80 | content = line.content.substring(getPrefix(line).length); 81 | } else { 82 | content = line.content; 83 | } 84 | 85 | if ( 86 | line.lineType == IoLineType.INPUT || 87 | line.lineType == IoLineType.PARAMETER 88 | ) { 89 | return replaceSpacesWithSpecialChar(content); 90 | } 91 | 92 | return content; 93 | } 94 | 95 | function replaceSpacesWithSpecialChar(input: string) { 96 | return input.replace(/ /g, "␣"); 97 | } 98 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/storage/CheckSerializer.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.storage; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonSyntaxException; 6 | import me.ialistannen.simplecodetester.checks.Check; 7 | import me.ialistannen.simplecodetester.checks.defaults.StaticInputOutputCheck; 8 | 9 | /** 10 | * A class that can (de-)serialize checks to/from json. 11 | */ 12 | public class CheckSerializer { 13 | 14 | private final Gson gson; 15 | 16 | /** 17 | * Creates a new check serializer. 18 | * 19 | * @param gson the gson instance to use 20 | */ 21 | public CheckSerializer(Gson gson) { 22 | this.gson = gson; 23 | } 24 | 25 | /** 26 | * Converts a check to json blob. 27 | * 28 | * @param check the check 29 | * @return a json blob representing thr check 30 | */ 31 | public String toJson(Check check) { 32 | JsonObject jsonObject = new JsonObject(); 33 | jsonObject.addProperty("class", check.getClass().getName()); 34 | jsonObject.addProperty("value", gson.toJson(check)); 35 | 36 | return gson.toJson(jsonObject); 37 | } 38 | 39 | /** 40 | * Converts a string representation of a check to a {@link JsonObject}. 41 | * 42 | * @param checkJson the check's json 43 | * @return the created json object 44 | */ 45 | public JsonObject toJson(String checkJson) { 46 | return gson.fromJson(checkJson, JsonObject.class); 47 | } 48 | 49 | /** 50 | * Converts a check back from json. 51 | * 52 | * @param json the json representation 53 | * @return the read check 54 | */ 55 | public Check fromJson(String json) { 56 | try { 57 | JsonObject jsonObject = toJson(json); 58 | 59 | if (!jsonObject.has("class")) { 60 | // assume old check storage format 61 | return gson.fromJson(jsonObject, StaticInputOutputCheck.class); 62 | } 63 | 64 | @SuppressWarnings("unchecked") 65 | Class checkClass = (Class) Class 66 | .forName(jsonObject.getAsJsonPrimitive("class").getAsString()); 67 | 68 | if (!Check.class.isAssignableFrom(checkClass)) { 69 | throw new IllegalArgumentException("The check class did not extend check: " + checkClass); 70 | } 71 | 72 | String value = jsonObject.get("value").getAsString(); 73 | 74 | return gson.fromJson(value, checkClass); 75 | } catch (ClassNotFoundException e) { 76 | throw new IllegalArgumentException("Check class not found", e); 77 | } catch (JsonSyntaxException | NullPointerException e) { 78 | throw new IllegalArgumentException("Malformed json: " + e.getMessage() + "\n" + json); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/entities/CodeCheck.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.entities; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.annotations.JsonAdapter; 5 | import com.google.gson.stream.JsonReader; 6 | import com.google.gson.stream.JsonWriter; 7 | import jakarta.validation.constraints.NotEmpty; 8 | import java.io.IOException; 9 | import java.time.Instant; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import lombok.AllArgsConstructor; 13 | import lombok.Getter; 14 | import lombok.Setter; 15 | import lombok.ToString; 16 | import me.ialistannen.simplecodetester.checks.CheckType; 17 | 18 | @ToString 19 | @Getter 20 | @Setter 21 | @AllArgsConstructor 22 | public class CodeCheck { 23 | 24 | private Integer id; 25 | private String text; 26 | @JsonAdapter(SerializeNameOnly.class) 27 | private User creator; 28 | private CheckCategory category; 29 | private Instant creationTime; 30 | private Instant updateTime; 31 | /** 32 | * Whether the check is approved and allowed to run. 33 | */ 34 | private boolean approved; 35 | private CheckType checkType = CheckType.INTERLEAVED_IO; 36 | private String name; 37 | 38 | /** 39 | * Creates a new CodeCheck. 40 | * 41 | * @param text the text 42 | * @param creator the creator 43 | * @param category the {@link CheckCategory} 44 | * @param approved whether this check is approved 45 | * @param name the name of the check 46 | */ 47 | public CodeCheck(@NotEmpty String text, User creator, CheckCategory category, String name, 48 | boolean approved) { 49 | this.text = text; 50 | this.creator = creator; 51 | this.category = category; 52 | this.creationTime = Instant.now(); 53 | this.name = name; 54 | this.approved = approved; 55 | } 56 | 57 | public Optional getUpdateTime() { 58 | return Optional.ofNullable(updateTime); 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | CodeCheck codeCheck = (CodeCheck) o; 70 | return Objects.equals(id, codeCheck.id); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(id); 76 | } 77 | 78 | private static class SerializeNameOnly extends TypeAdapter { 79 | 80 | @Override 81 | public void write(JsonWriter out, User user) throws IOException { 82 | out.value(user.getName()); 83 | } 84 | 85 | @Override 86 | public User read(JsonReader in) { 87 | throw new IllegalStateException("You can not deserialize this field"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/Check.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import me.ialistannen.simplecodetester.exceptions.CheckFailedException; 8 | import me.ialistannen.simplecodetester.submission.CompiledFile; 9 | import org.objectweb.asm.ClassReader; 10 | import org.objectweb.asm.ClassVisitor; 11 | 12 | /** 13 | * A single check that can validate some class properties. It accepts a single {@link CompiledFile} 14 | * and can use its source or bytecode to make a decision. 15 | */ 16 | @FunctionalInterface 17 | public interface Check { 18 | 19 | /** 20 | * Checks a single {@link CompiledFile}. 21 | * 22 | * @param file the file to check 23 | * @return the result of checking the file 24 | * @throws CheckFailedException if the check failed and it makes more sense for the check to not 25 | * directly return a result 26 | */ 27 | CheckResult check(CompiledFile file); 28 | 29 | /** 30 | * Returns the name of this check. 31 | * 32 | * @return the check's name 33 | * @implNote The default implementation just uses the class's simple name. 34 | */ 35 | default String name() { 36 | return getClass().getSimpleName(); 37 | } 38 | 39 | /** 40 | * Sets the additional files this check uses. 41 | * 42 | * @param files the files the check uses 43 | */ 44 | default void setFiles(Collection files) { 45 | } 46 | 47 | /** 48 | * Returns all files for this check. 49 | * 50 | * @return all files for this check 51 | * @see #setFiles(Collection) 52 | */ 53 | default Collection getFiles() { 54 | return Collections.emptyList(); 55 | } 56 | 57 | /** 58 | * Whether this check needs to be manually approved by an administrator. 59 | * 60 | * @return true if the check needs to be manually approved by an administrator 61 | */ 62 | default boolean needsApproval() { 63 | return true; 64 | } 65 | 66 | /** 67 | * Visits the given class file using the passed {@link ClassVisitor}. 68 | * 69 | * @param file the {@link CompiledFile} to visit 70 | * @param visitor the visitor to use 71 | */ 72 | default void visitClassfile(CompiledFile file, ClassVisitor visitor) { 73 | ClassReader classReader = new ClassReader(file.classFile()); 74 | classReader.accept(visitor, ClassReader.SKIP_DEBUG); 75 | } 76 | 77 | @ToString 78 | @Getter 79 | class CheckFile { 80 | 81 | private final String name; 82 | private final String content; 83 | 84 | public CheckFile(String name, String content) { 85 | this.name = name; 86 | this.content = content; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/db/repos/CheckCategoryRepository.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.db.repos; 2 | 3 | import static org.jooq.codegen.db.tables.CheckCategory.CHECK_CATEGORY; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.stream.Collectors; 8 | import me.ialistannen.simplecodetester.backend.db.entities.CheckCategory; 9 | import me.ialistannen.simplecodetester.backend.db.storage.DBReadAccess; 10 | import me.ialistannen.simplecodetester.backend.db.storage.DBWriteAccess; 11 | import me.ialistannen.simplecodetester.backend.db.storage.DatabaseStorage; 12 | import org.jooq.codegen.db.tables.records.CheckCategoryRecord; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | public class CheckCategoryRepository { 17 | 18 | private final DatabaseStorage databaseStorage; 19 | 20 | public CheckCategoryRepository(DatabaseStorage databaseStorage) { 21 | this.databaseStorage = databaseStorage; 22 | } 23 | 24 | public List findAll() { 25 | try (DBReadAccess db = databaseStorage.acquireReadAccess()) { 26 | return db.selectFrom(CHECK_CATEGORY) 27 | .stream() 28 | .map(this::recordToCheck) 29 | .collect(Collectors.toList()); 30 | } 31 | } 32 | 33 | public Optional findById(int id) { 34 | try (DBReadAccess db = databaseStorage.acquireReadAccess()) { 35 | return db.selectFrom(CHECK_CATEGORY) 36 | .where(CHECK_CATEGORY.ID.eq(id)) 37 | .fetchOptional() 38 | .map(this::recordToCheck); 39 | } 40 | } 41 | 42 | public CheckCategory save(String name) { 43 | try (DBWriteAccess db = databaseStorage.acquireWriteAccess()) { 44 | CheckCategoryRecord record = db.dsl().insertInto(CHECK_CATEGORY) 45 | .set(CHECK_CATEGORY.NAME, name) 46 | .returning() 47 | .fetchOne(); 48 | 49 | return recordToCheck(record); 50 | } 51 | } 52 | 53 | public int deleteById(int id) { 54 | try (DBWriteAccess db = databaseStorage.acquireWriteAccess()) { 55 | return db.deleteFrom(CHECK_CATEGORY) 56 | .where(CHECK_CATEGORY.ID.eq(id)) 57 | .execute(); 58 | } 59 | } 60 | 61 | public Optional rename(int id, String newName) { 62 | try (DBWriteAccess db = databaseStorage.acquireWriteAccess()) { 63 | return db.update(CHECK_CATEGORY) 64 | .set(CHECK_CATEGORY.NAME, newName) 65 | .where(CHECK_CATEGORY.ID.eq(id)) 66 | .returning() 67 | .fetchOptional() 68 | .map(this::recordToCheck); 69 | } 70 | } 71 | 72 | private CheckCategory recordToCheck(CheckCategoryRecord record) { 73 | return new CheckCategory(record.getId(), record.getName()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/services/user/UserService.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.user; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Consumer; 5 | import javax.transaction.Transactional; 6 | import me.ialistannen.simplecodetester.backend.db.entities.User; 7 | import me.ialistannen.simplecodetester.backend.db.repos.UserRepository; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class UserService { 12 | 13 | private final UserRepository userRepository; 14 | 15 | public UserService(UserRepository userRepository) { 16 | this.userRepository = userRepository; 17 | } 18 | 19 | /** 20 | * Adds a new user. 21 | * 22 | * @param user the user to add 23 | * @throws IllegalArgumentException if the user already existed 24 | */ 25 | public void addUser(User user) { 26 | if (containsUser(user.getId())) { 27 | throw new IllegalArgumentException("User did already exist!"); 28 | } 29 | userRepository.save(user); 30 | } 31 | 32 | /** 33 | * Deletes a given user. 34 | * 35 | * @param userId the id of the user to delete 36 | * @return true if the user was deleted, false if it did not exist 37 | */ 38 | public boolean removeUser(String userId) { 39 | return userRepository.deleteUserById(userId) != 0; 40 | } 41 | 42 | /** 43 | * Deletes all users. 44 | */ 45 | public void removeAll() { 46 | userRepository.deleteAll(); 47 | } 48 | 49 | /** 50 | * Checks if a given user already exists. 51 | * 52 | * @param userId the id of the user to check for 53 | * @return true if the user already exists 54 | */ 55 | public boolean containsUser(String userId) { 56 | return userRepository.existsUserById(userId); 57 | } 58 | 59 | /** 60 | * Finds a given user. 61 | * 62 | * @param userId the id of the user to delete 63 | * @return the found user, if any 64 | */ 65 | public Optional getUser(String userId) { 66 | return userRepository.findById(userId); 67 | } 68 | 69 | /** 70 | * Updates a single user. 71 | * 72 | * @param userId the id of the user to update 73 | * @param update the update method 74 | * @return true if the user existed 75 | */ 76 | @Transactional 77 | public boolean updateUser(String userId, Consumer update) { 78 | if (!userRepository.existsUserById(userId)) { 79 | return false; 80 | } 81 | User user = userRepository.findById(userId).orElseThrow(); 82 | update.accept(user); 83 | userRepository.save(user); 84 | 85 | return true; 86 | } 87 | 88 | /** 89 | * Returns all users. 90 | * 91 | * @return all users 92 | */ 93 | public Iterable getAllUsers() { 94 | return userRepository.findAll(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/checks/defaults/io/MatcherBlock.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.checks.defaults.io; 2 | 3 | import java.util.List; 4 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 5 | 6 | /** 7 | * A block containing {@link InterleavedIoMatcher}s. 8 | */ 9 | class MatcherBlock { 10 | 11 | private Block matcherBlock; 12 | 13 | /** 14 | * Creates a new Matcher block. 15 | * 16 | * @param matchers the matchers to use 17 | */ 18 | MatcherBlock(List matchers) { 19 | this.matcherBlock = new Block<>(matchers); 20 | } 21 | 22 | private MatcherBlock(Block block) { 23 | this.matcherBlock = block; 24 | } 25 | 26 | /** 27 | * Matches the output and generates a {@link ResultBlock}. 28 | * 29 | * @param outputBlock the program output 30 | * @return the generated {@link ResultBlock} 31 | */ 32 | ResultBlock match(Block outputBlock) { 33 | ResultBlock block = new ResultBlock(); 34 | 35 | while (matcherBlock.hasNext() && outputBlock.hasNext()) { 36 | InterleavedIoMatcher matcher = matcherBlock.next(); 37 | 38 | outputBlock.mark(); 39 | 40 | if (!matcher.match(outputBlock)) { 41 | outputBlock.getFromLastMark().forEach(block::addOutput); 42 | block.addError(matcher.getError()); 43 | } else { 44 | outputBlock.getFromLastMark().forEach(block::addOutput); 45 | } 46 | } 47 | 48 | while (matcherBlock.hasNext()) { 49 | InterleavedIoMatcher matcher = matcherBlock.next(); 50 | if (matcher.getError() != null) { 51 | block.addError(matcher.getError()); 52 | } else { 53 | block.addOther(matcher.toString()); 54 | } 55 | } 56 | 57 | while (outputBlock.hasNext()) { 58 | String output = outputBlock.next(); 59 | block.addOutput(output); 60 | block.addError("Did not expect any output."); 61 | } 62 | 63 | return block; 64 | } 65 | 66 | /** 67 | * Returns whether there is any matcher left. 68 | * 69 | * @return true if there is any matcher left 70 | */ 71 | boolean hasNext() { 72 | return matcherBlock.hasNext(); 73 | } 74 | 75 | /** 76 | * Returns the next {@link InterleavedIoMatcher}. 77 | * 78 | * @return the next matcher 79 | */ 80 | InterleavedIoMatcher next() { 81 | return matcherBlock.next(); 82 | } 83 | 84 | /** 85 | * Returns a version of this block reading from the start. Might be this instance, might be a new 86 | * one. 87 | * 88 | * @return an instance of this block reading from the start 89 | */ 90 | MatcherBlock reset() { 91 | return matcherBlock.isAtStart() ? this : new MatcherBlock(this.matcherBlock.copyFromStart()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/CheckCategoryEndpoint.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints; 2 | 3 | import jakarta.validation.constraints.NotEmpty; 4 | import java.util.List; 5 | import lombok.extern.slf4j.Slf4j; 6 | import me.ialistannen.simplecodetester.backend.db.entities.CheckCategory; 7 | import me.ialistannen.simplecodetester.backend.services.checks.CheckCategoryService; 8 | import me.ialistannen.simplecodetester.backend.util.ResponseUtil; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PatchMapping; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | @Slf4j 22 | public class CheckCategoryEndpoint { 23 | 24 | private CheckCategoryService checkCategoryService; 25 | 26 | public CheckCategoryEndpoint(CheckCategoryService checkCategoryService) { 27 | this.checkCategoryService = checkCategoryService; 28 | } 29 | 30 | @GetMapping("/check-category/get-all") 31 | public List getAll() { 32 | return checkCategoryService.getAll(); 33 | } 34 | 35 | @GetMapping("/check-category/get") 36 | public ResponseEntity getForId(@RequestParam int id) { 37 | return ResponseEntity.ok(checkCategoryService.getById(id)); 38 | } 39 | 40 | @PatchMapping("/check-category/rename") 41 | public ResponseEntity rename(@RequestParam int id, @RequestParam String newName) { 42 | return ResponseEntity.ok(checkCategoryService.rename(id, newName)); 43 | } 44 | 45 | @DeleteMapping("/check-category/delete/{id}") 46 | public ResponseEntity delete(@PathVariable("id") int id) { 47 | if (checkCategoryService.removeCategory(id)) { 48 | String user = SecurityContextHolder.getContext().getAuthentication().getName(); 49 | log.info("{} deleted the category {}", user, id); 50 | return ResponseEntity.ok("{}"); 51 | } 52 | return ResponseUtil.error(HttpStatus.NOT_FOUND, "Check not found!"); 53 | } 54 | 55 | @PostMapping("/check-category/add-new") 56 | public ResponseEntity addNew(@RequestParam @NotEmpty String name) { 57 | String user = SecurityContextHolder.getContext().getAuthentication().getName(); 58 | log.info("{} added the category {}", user, name); 59 | return ResponseEntity.ok(checkCategoryService.addCategory(name)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/crud/CrudModifyActions.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 91 | 92 | 93 | 95 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/main/java/me/ialistannen/simplecodetester/util/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import lombok.experimental.UtilityClass; 12 | import me.ialistannen.simplecodetester.exceptions.SuppressStacktrace; 13 | import org.joor.ReflectException; 14 | 15 | @UtilityClass 16 | public class ExceptionUtil { 17 | 18 | /** 19 | * Returns the stacktrace of a {@link Throwable} as a string. 20 | * 21 | * @param e the {@link Throwable} 22 | * @return the stacktrace of it 23 | */ 24 | public static String getStacktrace(Throwable e) { 25 | StringWriter stringWriter = new StringWriter(); 26 | PrintWriter printWriter = new PrintWriter(stringWriter); 27 | e.printStackTrace(printWriter); 28 | 29 | return stringWriter.getBuffer().toString(); 30 | } 31 | 32 | /** 33 | * Finds the root cause of an exception. 34 | * 35 | * @param throwable the exception 36 | * @return the root cause 37 | */ 38 | public static Throwable findRootCause(Throwable throwable) { 39 | if (throwable.getCause() != null) { 40 | return findRootCause(throwable.getCause()); 41 | } 42 | return throwable; 43 | } 44 | 45 | /** 46 | * Returns the exception name, message and an excerpt from the stacktrace. 47 | * 48 | * @param e the exception to analyze 49 | * @return the exception name, message and a stacktrace excerpt 50 | */ 51 | public static List getRelevantStackTraceAndMessage(ReflectException e) { 52 | Throwable exceptionToAnalyze = e.getCause(); 53 | 54 | if (exceptionToAnalyze instanceof InvocationTargetException) { 55 | exceptionToAnalyze = exceptionToAnalyze.getCause(); 56 | } 57 | 58 | if (exceptionToAnalyze.getClass().isAnnotationPresent(SuppressStacktrace.class)) { 59 | return Collections.singletonList(exceptionToAnalyze.getMessage()); 60 | } 61 | 62 | List result = new ArrayList<>(); 63 | 64 | result.add(exceptionToAnalyze.getClass().getSimpleName()); 65 | 66 | if (exceptionToAnalyze.getMessage() != null) { 67 | result.add(exceptionToAnalyze.getMessage()); 68 | } 69 | 70 | List stacktrace = Arrays.stream(getStacktrace(exceptionToAnalyze).split("\n")) 71 | // only take the user's frames. Reflection is the marker that *our* code follows 72 | // s tad crude, but good enough in this case as reflection is blocked 73 | .takeWhile(s -> !s.contains("jdk.internal.reflect")) 74 | .collect(Collectors.toList()); 75 | 76 | result.addAll(stacktrace); 77 | 78 | return result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SimpleCodeTester-Lib/src/test/java/me/ialistannen/simplecodetester/util/ConfiguredGsonTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import com.google.gson.Gson; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.List; 10 | import java.util.stream.Stream; 11 | import me.ialistannen.simplecodetester.checks.defaults.StaticInputOutputCheck; 12 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.ErrorIoMatcher; 13 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.InterleavedIoMatcher; 14 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.LiteralIoMatcher; 15 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.RegularExpressionIoMatcher; 16 | import me.ialistannen.simplecodetester.checks.defaults.io.matcher.VerbatimInputMatcher; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.params.ParameterizedTest; 20 | import org.junit.jupiter.params.provider.Arguments; 21 | import org.junit.jupiter.params.provider.MethodSource; 22 | 23 | class ConfiguredGsonTest { 24 | 25 | private Gson gson; 26 | 27 | @BeforeEach 28 | void setup() { 29 | gson = ConfiguredGson.createGson(); 30 | } 31 | 32 | @Test 33 | void canHandlePaths() { 34 | Path path = Paths.get("/randomPath"); 35 | 36 | assertEquals( 37 | path, 38 | gson.fromJson(gson.toJson(path, Path.class), Path.class) 39 | ); 40 | } 41 | 42 | @Test 43 | void canHandleStaticInputOutputMatcher() { 44 | StaticInputOutputCheck check = new StaticInputOutputCheck( 45 | List.of("some", "input"), 46 | "THIS IS EXPECTED", 47 | "NiceCheck" 48 | ); 49 | assertEquals( 50 | check, 51 | gson.fromJson(gson.toJson(check), StaticInputOutputCheck.class) 52 | ); 53 | } 54 | 55 | @ParameterizedTest 56 | @MethodSource("generateIoMatcher") 57 | void canHandleIoMatcher(InterleavedIoMatcher matcher) { 58 | assertEquals( 59 | matcher, 60 | gson.fromJson(gson.toJson(matcher, InterleavedIoMatcher.class), InterleavedIoMatcher.class) 61 | ); 62 | } 63 | 64 | private static Stream generateIoMatcher() { 65 | return Stream.of( 66 | Arguments.of(new LiteralIoMatcher("hey")), 67 | Arguments.of(new VerbatimInputMatcher("hello")), 68 | Arguments.of(new RegularExpressionIoMatcher(".+")) 69 | ); 70 | } 71 | 72 | @Test 73 | void canHandleErrorIoMatcher() { 74 | String json = gson.toJson(new ErrorIoMatcher(), InterleavedIoMatcher.class); 75 | 76 | InterleavedIoMatcher fromJson = gson.fromJson(json, InterleavedIoMatcher.class); 77 | 78 | assertTrue( 79 | fromJson instanceof ErrorIoMatcher, 80 | "Was no error matcher" 81 | ); 82 | } 83 | } -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/endpoints/runner/RunnerEndpoint.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.endpoints.runner; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import java.util.Optional; 5 | import java.util.UUID; 6 | import lombok.extern.slf4j.Slf4j; 7 | import me.ialistannen.simplecodetester.backend.services.checkrunning.TaskQueue; 8 | import me.ialistannen.simplecodetester.backend.services.config.RunnerConfig; 9 | import me.ialistannen.simplecodetester.result.Result; 10 | import me.ialistannen.simplecodetester.submission.CompleteTask; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestHeader; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RestController 20 | @Slf4j 21 | public class RunnerEndpoint { 22 | 23 | private final TaskQueue taskQueue; 24 | private final RunnerConfig runnerConfig; 25 | 26 | public RunnerEndpoint(TaskQueue taskQueue, RunnerConfig runnerConfig) { 27 | this.taskQueue = taskQueue; 28 | this.runnerConfig = runnerConfig; 29 | } 30 | 31 | @GetMapping("/request-work") 32 | public ResponseEntity requestWork(@RequestHeader("Authorization") String password) { 33 | if (!this.runnerConfig.getPassword().equals(password)) { 34 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 35 | } 36 | 37 | Optional taskOptional = taskQueue.pollTask(); 38 | 39 | taskOptional.ifPresent( 40 | task -> log.info("Sending task for ({}) to runner", task.userIdentifier()) 41 | ); 42 | 43 | return ResponseEntity.of(taskOptional); 44 | } 45 | 46 | @PostMapping("/report-work") 47 | public ResponseEntity reportWork( 48 | @RequestHeader("Authorization") String password, 49 | @RequestBody @NotNull AttributedResult result 50 | ) { 51 | if (!this.runnerConfig.getPassword().equals(password)) { 52 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 53 | } 54 | log.info("Received results for ({})", result.getUserId()); 55 | 56 | taskQueue.complete(result.getResult(), result.getUserId()); 57 | return ResponseEntity.ok().build(); 58 | } 59 | 60 | private static class AttributedResult { 61 | 62 | private final String userId; 63 | private final Result result; 64 | 65 | public AttributedResult(String userId, Result result) { 66 | this.userId = userId; 67 | this.result = result; 68 | } 69 | 70 | public UUID getUserId() { 71 | return UUID.fromString(userId); 72 | } 73 | 74 | public Result getResult() { 75 | return result; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/main/java/me/ialistannen/simplecodetester/backend/security/JwtFilter.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.security; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.FilterChain; 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.jose4j.jwt.JwtClaims; 10 | import org.jose4j.jwt.MalformedClaimException; 11 | import org.jose4j.jwt.consumer.InvalidJwtException; 12 | import org.jose4j.jwt.consumer.JwtConsumer; 13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 16 | import org.springframework.web.filter.OncePerRequestFilter; 17 | 18 | /** 19 | * A {@link javax.servlet.Filter} that logs the user in based on a JWT: 20 | */ 21 | @Slf4j 22 | public class JwtFilter extends OncePerRequestFilter { 23 | 24 | private static final String PREFIX = "Bearer "; 25 | private final JwtConsumer jwtConsumer; 26 | 27 | /** 28 | * Creates a new JwtFilter with the given {@link JwtConsumer}. 29 | * 30 | * @param jwtConsumer the {@link JwtConsumer} to use 31 | */ 32 | JwtFilter(JwtConsumer jwtConsumer) { 33 | this.jwtConsumer = jwtConsumer; 34 | } 35 | 36 | @Override 37 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 38 | FilterChain chain) throws ServletException, IOException { 39 | 40 | String requestHeader = request.getHeader("Authorization"); 41 | 42 | if (requestHeader == null || !requestHeader.startsWith(PREFIX)) { 43 | logger.debug("Invalid auth received, no header present"); 44 | chain.doFilter(request, response); 45 | return; 46 | } 47 | String token = requestHeader.substring(PREFIX.length()).trim(); 48 | 49 | // already authenticated 50 | if (SecurityContextHolder.getContext().getAuthentication() != null) { 51 | log.info( 52 | "Already authenticated: '{}'", 53 | SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString() 54 | ); 55 | chain.doFilter(request, response); 56 | return; 57 | } 58 | 59 | try { 60 | JwtClaims jwtClaims = jwtConsumer.processToClaims(token); 61 | 62 | AuthenticatedJwtUser authenticatedUser = new AuthenticatedJwtUser(jwtClaims); 63 | 64 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( 65 | authenticatedUser, null, authenticatedUser.getAuthorities() 66 | ); 67 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 68 | SecurityContextHolder.getContext().setAuthentication(authentication); 69 | } catch (InvalidJwtException | MalformedClaimException e) { 70 | log.info("JWT is no longer valid for '{}'", token); 71 | } 72 | 73 | chain.doFilter(request, response); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/checksubmit/IOCheckComponent.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 103 | 104 | 109 | -------------------------------------------------------------------------------- /SimpleCodeTester-Backend/src/test/java/me/ialistannen/simplecodetester/backend/services/user/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package me.ialistannen.simplecodetester.backend.services.user; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.function.Supplier; 10 | import lombok.SneakyThrows; 11 | import me.ialistannen.simplecodetester.backend.db.entities.User; 12 | import me.ialistannen.simplecodetester.backend.services.config.DatabaseConfig; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.extension.ExtendWith; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Primary; 20 | import org.springframework.test.context.junit.jupiter.SpringExtension; 21 | 22 | @ExtendWith(SpringExtension.class) 23 | @SpringBootTest 24 | class UserServiceTest { 25 | 26 | @Autowired 27 | private UserService userService; 28 | 29 | @BeforeEach 30 | void setupUser() { 31 | userService.removeAll(); 32 | } 33 | 34 | @Test 35 | void addUser() { 36 | User user = new User("Test", "User", "31", true, List.of()); 37 | userService.addUser(user); 38 | 39 | assertThat( 40 | userService.getUser("Test"), 41 | is(Optional.of(user)) 42 | ); 43 | } 44 | 45 | @Test 46 | void addMultipleUser() { 47 | Supplier userSupplier = () -> new User("Test", "User", "31", true, List.of()); 48 | User user = userSupplier.get(); 49 | userService.addUser(user); 50 | 51 | assertThat( 52 | userService.getUser("Test"), 53 | is(Optional.of(user)) 54 | ); 55 | 56 | assertThrows( 57 | IllegalArgumentException.class, 58 | () -> userService.addUser(userSupplier.get()) 59 | ); 60 | } 61 | 62 | @Test 63 | void addDeleteUser() { 64 | User user = new User("Test", "User", "31", true, List.of()); 65 | userService.addUser(user); 66 | 67 | assertThat( 68 | userService.getUser("Test"), 69 | is(Optional.of(user)) 70 | ); 71 | 72 | assertThat( 73 | userService.removeUser("Test"), 74 | is(true) 75 | ); 76 | 77 | assertThat( 78 | userService.getUser("Test"), 79 | is(Optional.empty()) 80 | ); 81 | } 82 | 83 | @Test 84 | void updateUserName() { 85 | User user = new User("Test", "User", "31", true, List.of()); 86 | userService.addUser(user); 87 | 88 | assertThat( 89 | userService.getUser("Test"), 90 | is(Optional.of(user)) 91 | ); 92 | 93 | assertThat( 94 | userService.updateUser("Test", u -> u.setEnabled(false)), 95 | is(true) 96 | ); 97 | 98 | assertThat( 99 | userService.getUser("Test").map(User::isEnabled), 100 | is(Optional.of(false)) 101 | ); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/highlighting/HighlightedCode.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 59 | 60 | 67 | 68 | 114 | -------------------------------------------------------------------------------- /simplecodetester-frontend/src/components/Profile.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 93 | 94 | 96 | --------------------------------------------------------------------------------