diff --git a/i18n/en.ts b/i18n/en.ts index 8651937dfb23560bce3c81c7074446422b45663d..8bb265074c49eb0d851b78248fbce7a9185d2eb8 100644 --- a/i18n/en.ts +++ b/i18n/en.ts @@ -914,6 +914,10 @@ export const lang_en: LangObj = { "Save test server debug logs": "Save test server debug logs", "If true the test server debug protocol will be saved in the test result (increases needed space by a lot ~5k chars) per test result": "If true the test server debug protocol will be saved in the test result (increases needed space by a lot ~5k chars) per test result", + //--- external checker test + "Evaluate user solution (true/false)": "Evaluate user solution (true/false)", + "true: the checker should evaluate the user solution and output the scored points on the first line, false: do not evaluate the user solution, e.g. only check syntax": "true: the checker should evaluate the user solution and output the scored points on the first line, false: do not evaluate the user solution, e.g. only check syntax", + //--- compare test "Compare files options" : "Compare files options", "Ignore case": "Ignore case", diff --git a/i18n/i18nRoot.ts b/i18n/i18nRoot.ts index eb7137d084a87356c9a154a6cdd9c53aafbf4955..610c8a396894b74f4c7fa242704427bb99072b82 100644 --- a/i18n/i18nRoot.ts +++ b/i18n/i18nRoot.ts @@ -918,6 +918,10 @@ export interface LangObj { "Save test server debug logs": string "If true the test server debug protocol will be saved in the test result (increases needed space by a lot ~5k chars) per test result": string + //--- external checker test + "Evaluate user solution (true/false)": string + "true: the checker should evaluate the user solution and output the scored points on the first line, false: do not evaluate the user solution, e.g. only check syntax": string + //--- compare test "Compare files options": string "Ignore case": string diff --git a/src/components/sites/doExerciseSite/afterSubmitTestsPanel/afterSubmitTestsPanelHeaderBar.tsx b/src/components/sites/doExerciseSite/afterSubmitTestsPanel/afterSubmitTestsPanelHeaderBar.tsx index 3796e38116e55de4b5cbe3f282e87b66e4d2f069..1c2224fe4fe766134ac3f1a76f04934d7957edee 100644 --- a/src/components/sites/doExerciseSite/afterSubmitTestsPanel/afterSubmitTestsPanelHeaderBar.tsx +++ b/src/components/sites/doExerciseSite/afterSubmitTestsPanel/afterSubmitTestsPanelHeaderBar.tsx @@ -147,12 +147,12 @@ class submitTestsPanelHeaderBar extends React.Component<Props, any> { render(): JSX.Element { const maxSummedPoints = this.props.submitTestResults - .map(p => p.weight) + .map(p => p.maxPoints) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) const reachedSummedPoints = this.props.submitTestResults .map(p => p.result && p.result.passed - ? p.weight + ? p.maxPoints : 0) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) @@ -245,4 +245,4 @@ class submitTestsPanelHeaderBar extends React.Component<Props, any> { } } -export default connect(mapStateToProps, mapDispatchToProps)(submitTestsPanelHeaderBar) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(submitTestsPanelHeaderBar) diff --git a/src/components/sites/doExerciseSite/consolePanel/consolePanelView.tsx b/src/components/sites/doExerciseSite/consolePanel/consolePanelView.tsx index 9a17c79e3d7f81a93a58a59ec0bd3af0585a69f5..719687f45c14ade3a21a723e8492a1eec3200026 100644 --- a/src/components/sites/doExerciseSite/consolePanel/consolePanelView.tsx +++ b/src/components/sites/doExerciseSite/consolePanel/consolePanelView.tsx @@ -113,6 +113,10 @@ class ConsolePanelView extends React.Component<Props, any> { // Logger.debug('consolePanelView', 'using protocol items as content') } + if (!protocolItems) { + return null + } + //only allow if we have expected output lines //maybe this is too strange because you cannot toggle the item and then one might forget to uncheck it... const canHideExpectedOutputLines = true //protocolItems.some(p => p.type === TestProtocolType.isExpectedOutput) diff --git a/src/components/sites/doExerciseSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx b/src/components/sites/doExerciseSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx index 50e60846e43315e70b00772ade0961bcc9c62991..20e4a50b7798e5413b054eeb97cbeb66f8e061cc 100644 --- a/src/components/sites/doExerciseSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx +++ b/src/components/sites/doExerciseSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx @@ -46,11 +46,11 @@ class submitTestsPanelHeaderBar extends React.Component<Props, any> { render(): JSX.Element { const maxSummedPoints = this.props.submitTestResults - .map(p => p.weight) + .map(p => p.maxPoints) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) const reachedSummedPoints = this.props.submitTestResults - .map(p => p.result && p.result.passed ? p.weight : 0) + .map(p => p.result && p.result.passed ? p.maxPoints : 0) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) // noinspection TsLint @@ -95,4 +95,4 @@ class submitTestsPanelHeaderBar extends React.Component<Props, any> { } } -export default connect(mapStateToProps, mapDispatchToProps)(submitTestsPanelHeaderBar) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(submitTestsPanelHeaderBar) diff --git a/src/components/sites/doExerciseSite/testsPanel/testsPanelView.tsx b/src/components/sites/doExerciseSite/testsPanel/testsPanelView.tsx index 6528e8564452b8a2fe85d902138960acf249a2aa..8dcd866f16d1db49d51a2571b80969320b844987 100644 --- a/src/components/sites/doExerciseSite/testsPanel/testsPanelView.tsx +++ b/src/components/sites/doExerciseSite/testsPanel/testsPanelView.tsx @@ -125,7 +125,7 @@ class TestsPanel extends React.Component<Props, any> { render(): JSX.Element { const maxSummedPoints = this.props.tests - .map(p => p.weight) + .map(p => p.maxPoints) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) return ( @@ -148,7 +148,7 @@ class TestsPanel extends React.Component<Props, any> { { this.props.displayWeightIndicatorsForResults && - <WeightIndicatorBackgroundBar value={test.weight} + <WeightIndicatorBackgroundBar value={test.maxPoints} maxValue={maxSummedPoints} indicatorColor={ test.result && test.result.passed ? indicatorPassedBgColor : indicatorNotPassedBgColor @@ -210,6 +210,13 @@ class TestsPanel extends React.Component<Props, any> { } </div> + { + (test.result && test.result.points !== null) && + <div className="v-centered mar-right"> + {test.result.points} / { test.maxPoints } + </div> + } + <div className="clickable v-centered" onClick={() => { this.props.openViewTestDialog(test) diff --git a/src/components/sites/doExerciseSite/testsPanel/viewTestDialog/viewTestView.tsx b/src/components/sites/doExerciseSite/testsPanel/viewTestDialog/viewTestView.tsx index 4bf259bd30971703b87d98aeb6f17ee1785f9570..13be312a817b31efcc2e7f63f8ed358ea7cb951f 100644 --- a/src/components/sites/doExerciseSite/testsPanel/viewTestDialog/viewTestView.tsx +++ b/src/components/sites/doExerciseSite/testsPanel/viewTestDialog/viewTestView.tsx @@ -11,7 +11,7 @@ import {Form, Icon, List, Tab} from "semantic-ui-react"; import MaterialInput from '../../../../material/materialInput' import SimpleCodeEditorWrapper from '../../../../codeEditors/simpleCodeEditorWrapper' import ConsolePanelView from '../../consolePanel/consolePanelView' -import {TestProtocolItem} from "../../../../../types/testResults"; +import {CompareTestOptions, ExternalCheckerTestOptions, TestProtocolItem} from "../../../../../types/testResults"; import { convertSizeToHumanReadableSize, convertTestProtocolFromString @@ -32,6 +32,7 @@ import CompareFilesTestSettingsView from '../../../exerciseEditorSite/exerciseTestsPanel/dialogs/compareFilesTestSettingsView' import TestSettingsView from '../../../../helpers/testing/testSettingsView' import {TestTypeLimits} from '../../../../../constants' +import {HelpPopup} from '../../../../helpers/helpPopup' //const css = require('./styles.styl'); @@ -98,6 +99,8 @@ class viewTestView extends React.Component<Props, any> { const isCompareFilesTest = testType.internalName === KnownInternalTestTypes.compareFilesTest + const isExternalCheckerTest = testType.internalName === KnownInternalTestTypes.externalCheckerTest + return ( <div className="fh flexed-h"> <div className="ui form divided-checkboxes fh" style={{height: 'auto' /*firefox fix*/}}> @@ -125,7 +128,7 @@ class viewTestView extends React.Component<Props, any> { } </label> <MaterialInput - value={this.props.test.weight} + value={this.props.test.maxPoints} disabled /> </Form.Field> @@ -181,6 +184,21 @@ class viewTestView extends React.Component<Props, any> { </div> } + { + isExternalCheckerTest && + <div> + <div className="flexed mar-bot"> + <div className="mar-left"> + <span className="mar-right-half">{ExternalCheckerTestOptions.evaluate.toString()}: {getI18n( + this.props.langId, "Evaluate user solution (true/false)")}</span> + <HelpPopup wide defaultText={getI18n(this.props.langId, + "true: the checker should evaluate the user solution and output the scored points on the first line, false: do not evaluate the user solution, e.g. only check syntax" + )}/> + </div> + </div> + </div> + } + <div className="flexed-h flex-stretch-0"> { diff --git a/src/components/sites/editCustomProjectSite/dialogs/changeTestDialog/changeTestView.tsx b/src/components/sites/editCustomProjectSite/dialogs/changeTestDialog/changeTestView.tsx index 42e5033b5a968f5de983a95a8a28873395766b46..7bbfa0ba0d5cf39d944089c36a0cb429d308fb79 100644 --- a/src/components/sites/editCustomProjectSite/dialogs/changeTestDialog/changeTestView.tsx +++ b/src/components/sites/editCustomProjectSite/dialogs/changeTestDialog/changeTestView.tsx @@ -422,8 +422,8 @@ class ChangeTestView extends React.Component<Props, any> { } </label> <MaterialInput type="number" - validations={validationRules.weight} - validationMessageKey={validationMessageKeys.weight} + validations={validationRules.maxPoints} + validationMessageKey={validationMessageKeys.maxPoints} value={this.props.test.weight} onChange={(e) => { diff --git a/src/components/sites/exerciseEditorSite/exerciseTestsPanel/dialogs/changeTestView.tsx b/src/components/sites/exerciseEditorSite/exerciseTestsPanel/dialogs/changeTestView.tsx index 71f1d7051fcf9c7a3765d2abe569b960c77841e0..799d1b52b65b33dca86398f3a5511526148f28a5 100644 --- a/src/components/sites/exerciseEditorSite/exerciseTestsPanel/dialogs/changeTestView.tsx +++ b/src/components/sites/exerciseEditorSite/exerciseTestsPanel/dialogs/changeTestView.tsx @@ -424,9 +424,9 @@ class ChangeTestView extends React.Component<Props, any> { } </label> <MaterialInput type="number" - validations={validationRules.weight} - validationMessageKey={validationMessageKeys.weight} - value={this.props.test.weight} + validations={validationRules.maxPoints} + validationMessageKey={validationMessageKeys.maxPoints} + value={this.props.test.maxPoints} onChange={(e) => { if (e.currentTarget.value === '') { diff --git a/src/components/sites/tutorViewSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx b/src/components/sites/tutorViewSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx index 307bdbefd8b27d0f9d44075cc76547bbd145b9f6..57dd26f429bb35fbf69f9c127f26276834603b1e 100644 --- a/src/components/sites/tutorViewSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx +++ b/src/components/sites/tutorViewSite/submitTestsPanel/submitTestsPanelHeaderBar.tsx @@ -66,11 +66,11 @@ class SubmitTestsPanelHeaderBar extends React.Component<Props, any> { render(): JSX.Element { const maxSummedPoints = this.props.tests - .map(p => p.weight) + .map(p => p.maxPoints) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) const reachedSummedPoints = this.props.tests - .map(p => p.result && p.result.passed ? p.weight : 0) + .map(p => p.result && p.result.passed ? p.maxPoints : 0) .reduce(((previousValue, currentValue) => previousValue + currentValue), 0) // noinspection TsLint diff --git a/src/constants.ts b/src/constants.ts index dce3709ad520fd819650ef9f13f9b5fdd085601b..bba31e3df1a12b8dd473c0db7cffc90ec3f539d9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -13,7 +13,7 @@ import Logger from './helpers/logger' * y - breaking changes / new features * z - fixes, small changes */ -export const versionString = '2.10.6' +export const versionString = '2.11.0' export const supportMail = 'yapex@informatik.uni-halle.de' diff --git a/src/helpers/convertersAndTransformers.ts b/src/helpers/convertersAndTransformers.ts index cfe7667dc45754a7410dbf912e4820d51fd493f7..47c47511b6a0e5ce6a8a17d506ee04de65e8f3c2 100644 --- a/src/helpers/convertersAndTransformers.ts +++ b/src/helpers/convertersAndTransformers.ts @@ -639,6 +639,7 @@ export function _storedTestResultToTestResult(storedResult: StoredTestResultFrom return { hasCompiled: storedResult.hasCompiled, passed: storedResult.passed, + points: storedResult.points, testId: storedResult.testId, programExitCode: storedResult.programExitCode, protocol: storedResult.protocol, @@ -876,7 +877,41 @@ export function convertTestProtocol(protocol: ReadonlyArray<string>, } }) - } else if (testType.internalName === KnownInternalTestTypes.regexTest) { + } + else if (testType.internalName === KnownInternalTestTypes.externalCheckerTest) { + + return protocol.map<TestProtocolItem>(line => { + + line = trimStart(line) //for some reason some lines start with whitespaces... + + if (startsWith(line, TestProtocol.outputSign)) { + + //some normal lines from the checker + return { + type: TestProtocolType.isOutput, + hasOutputMismatched: false, + content: line.substring(TestProtocol.outputSign.length), + } + + } + else if (startsWith(line, TestProtocol.errorPrefix)) { + return { + type: TestProtocolType.isError, + hasOutputMismatched: false, + content: line.substring(TestProtocol.errorPrefix.length), + } + } + + + return { + type: TestProtocolType.unknown, + hasOutputMismatched: false, + content: line, + } + }) + + } + else if (testType.internalName === KnownInternalTestTypes.regexTest) { //use plain output return protocol.map<TestProtocolItem>(line => { @@ -886,7 +921,8 @@ export function convertTestProtocol(protocol: ReadonlyArray<string>, content: line, } }) - } else if (testType.internalName === KnownInternalTestTypes.compareFilesTest) { + } + else if (testType.internalName === KnownInternalTestTypes.compareFilesTest) { const protocolItems = protocol @@ -964,7 +1000,8 @@ export function convertTestProtocol(protocol: ReadonlyArray<string>, return protocolItems - } else { + } + else { //TODO unknown test type Logger.warn("found unknown test protocol type (internal name)") } diff --git a/src/state/actions/doExerciseSite/doExerciseCrudActions.ts b/src/state/actions/doExerciseSite/doExerciseCrudActions.ts index 359a57461f32246f47a8b7d9ae135516cd4b0a05..3ee340cc4ad40f32e6ae76fd83ef195021274a7d 100644 --- a/src/state/actions/doExerciseSite/doExerciseCrudActions.ts +++ b/src/state/actions/doExerciseSite/doExerciseCrudActions.ts @@ -494,7 +494,7 @@ export function runCompileSingleFileDoExercise(runCompileSingleFileCommand: RunC uiIsRunning: false, files: [], displayIndex: -1, - weight: 0, + maxPoints: 0, isSubmitTest: false, uiIsAfterTest: false, testSettings: { @@ -546,7 +546,7 @@ export function justRunProgramDoExerciseAsync(justRunProgramCommand: JustRunProg uiIsRunning: false, files: [], displayIndex: -1, - weight: 0, + maxPoints: 0, isSubmitTest: false, uiIsAfterTest: false, testSettings: { diff --git a/src/state/actions/exerciseEditorSite/dialogs/changeTestDialog/changeTestActions.ts b/src/state/actions/exerciseEditorSite/dialogs/changeTestDialog/changeTestActions.ts index 7979302a835b15ef4096fc0e431944151543fa6f..c87cc9493f887bdc9ce1a899f9062d5c6ea17245 100644 --- a/src/state/actions/exerciseEditorSite/dialogs/changeTestDialog/changeTestActions.ts +++ b/src/state/actions/exerciseEditorSite/dialogs/changeTestDialog/changeTestActions.ts @@ -30,7 +30,7 @@ export function setChangTest(test: ExerciseTestForBackend): MultiActions { dispatch(setDisplayNameAction(test.displayName)) dispatch(setDisplayIndex(test.displayIndex)) dispatch(setIsSubmitTest(test.isSubmitTest)) - dispatch(setWeight(test.weight)) + dispatch(setWeight(test.maxPoints)) dispatch(setEditorTest_memoryLimitInKb(test.testSettings.memoryLimitInKb)) dispatch(setEditorTest_timeoutInMs(test.testSettings.timeoutInMs)) diff --git a/src/state/actions/tutorViewSite/tutorViewSiteCrudActions.ts b/src/state/actions/tutorViewSite/tutorViewSiteCrudActions.ts index fd75ba816ff542a34aa0e3fd2822109d630ced29..9766cde50ad2038ec8570906a8c85ee3674b9a17 100644 --- a/src/state/actions/tutorViewSite/tutorViewSiteCrudActions.ts +++ b/src/state/actions/tutorViewSite/tutorViewSiteCrudActions.ts @@ -450,7 +450,7 @@ export function runCompileSingleFileTutorView(runCompileSingleFileCommand: RunCo uiIsRunning: false, files: [], displayIndex: -1, - weight: 0, + maxPoints: 0, isSubmitTest: false, uiIsAfterTest: false, testSettings: { @@ -494,7 +494,7 @@ export function justRunProgramTutorViewAsync(justRunProgramCommand: JustRunProgr uiIsRunning: false, files: [], displayIndex: -1, - weight: 0, + maxPoints: 0, isSubmitTest: false, uiIsAfterTest: false, testSettings: { diff --git a/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducer.ts b/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducer.ts index 5d4e1d9b2659953aa7709c10dc921528f45ee1f5..cd6a36211bec6250a9dde8bb99861eaf7c866a45 100644 --- a/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducer.ts +++ b/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducer.ts @@ -24,7 +24,7 @@ export const initial: () => State = () => { displayName: '', displayIndex: -1, //use -1 and let determine the action the right index isSubmitTest: false, - weight: 1, + maxPoints: 1, files: [], testTypeId: -1, //TODO maybe validation?? @@ -46,7 +46,7 @@ export const validationRules = getValidationCollection<ExerciseTestForBackend>({ displayName: [isNotEmptyOrSpaces], displayIndex: [], isSubmitTest: [], - weight: [], + maxPoints: [], files: [], testTypeId: [isValidId], testSettings: [], @@ -173,7 +173,7 @@ export function reducer(state: State = initial(), action: AllActions): State { case ActionType.SET_weight: return { ...state, - weight: action.weight + maxPoints: action.weight } case ActionType.SET_testTypeId: diff --git a/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducerValidation.ts b/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducerValidation.ts index 5991988472b249e5be980ef5d8654a936ea3c5cb..adf5f2c03bf0ced9046b27ac9b59eb08dd8c01d7 100644 --- a/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducerValidation.ts +++ b/src/state/reducers/exerciseEditorSite/dialogs/changeTestDialog/editorTestReducerValidation.ts @@ -9,8 +9,8 @@ export const validationMessageKeys = getValidationMessagesCollection<ExerciseTes displayName: getI18n(globalState.getState().i18nState.langId,'Field must not be empty or just whitespaces'), displayIndex: '', isSubmitTest: '', - weight: '', + maxPoints: '', files: '', testTypeId: getI18n(globalState.getState().i18nState.langId,'Invalid option, take other'), testSettings: '', -}) \ No newline at end of file +}) diff --git a/src/types/exerciseEditor.ts b/src/types/exerciseEditor.ts index b17514cd46c45d243bcb0d54f30abc5296ccf039..de1013e3c13aa40d6ce1b2906574f5bf12cc6d07 100644 --- a/src/types/exerciseEditor.ts +++ b/src/types/exerciseEditor.ts @@ -296,7 +296,7 @@ export interface ExerciseTestBase { /** * the weight of the test */ - readonly weight: number + readonly maxPoints: number /** * ui property, the display index for the sorting in the frontend * (>= 0, 0 is first, 1 second, ...) diff --git a/src/types/testResults.ts b/src/types/testResults.ts index dc6e356e2cf18d239a725a1f7ec73a0a14b760c4..7e40d24fbecfe03c642c5e386d2f7672027a8eca 100644 --- a/src/types/testResults.ts +++ b/src/types/testResults.ts @@ -33,6 +33,10 @@ export interface StoredTestResultFromBackend { * null: not ran yet, true: test was successful, false: not */ readonly passed: boolean | null + /** + * null: not ran yet, the scored points + */ + readonly points: number | null /** * null: not ran yet, true: code compiled, false: not */ @@ -356,3 +360,8 @@ export enum CompareTestOptions { ignoreAllEmptyOrWhitespacesOnlyLines = 8 } + +export enum ExternalCheckerTestOptions { + evaluate = 'evaluate', + +} diff --git a/src/types/testTypes.ts b/src/types/testTypes.ts index 913d8f50e9595e30e33f2e4397371dc29b2999a7..025afec546cd547c90b96a5c03b0905d168f669e 100644 --- a/src/types/testTypes.ts +++ b/src/types/testTypes.ts @@ -29,6 +29,7 @@ export enum KnownInternalTestTypes { blackBoxTest = 'blackBoxTest', compileTest = 'compileTest', regexTest = 'regexTest', + externalCheckerTest = 'externalCheckerTest', /** * in this case the test content is * @see CompareFileTestContentFrontendOnly[] diff --git a/src/types/testsAndTestProtocol.ts b/src/types/testsAndTestProtocol.ts index 8876bbad72280682b7d65bd2e2794a54cdbbb1e7..a440532645c0a1ee4ba07574dee8e10be0fd14ee 100644 --- a/src/types/testsAndTestProtocol.ts +++ b/src/types/testsAndTestProtocol.ts @@ -490,6 +490,12 @@ export interface TestAnswerFromBackend { * true: test was successful, false: not */ readonly passed: boolean + /** + * the scored points + * can be more or less the max points for this test case + * null for normal tests, only set for external checkers + */ + readonly points: number | null /** * true: has compiled, fasle: not or if the test does not included a compile step this is also false */