/// <summary> /// Verifies that the given generated question template was /// correctly duplicated. /// </summary> private void VerifyGeneratedQuestionTemplate( DatabaseContext dbContext, GeneratedQuestionTemplate oldQuestion, GeneratedQuestionTemplate newQuestion) { // No navigation properties. }
/// <summary> /// Creates a class job that will generate the question, and return the result /// in the form of a json string. /// </summary> private ClassJob CreateQuestionGeneratorClassJob( GeneratedQuestionTemplate question, int seed) { var classesToImport = GetClassesToImport(question); int lineOffset = question.FullGeneratorFileLineOffset; return(new ClassJob() { ClassesToImport = classesToImport, ClassName = "QuestionGenerator", FileContents = !string.IsNullOrEmpty(question.FullGeneratorFileContents) ? question.FullGeneratorFileContents : GetGeneratorFile(question.GeneratorContents, out lineOffset), LineNumberOffset = lineOffset, Tests = new List <ClassTest>() { new ClassTest() { TestName = "QuestionGenerator", MethodBody = string.Join( "\n", new List <string>() { "ObjectMapper mapper = new ObjectMapper();", $"return mapper.writeValueAsString(QuestionGenerator.generateQuestion({seed}));" }), ReturnType = "String" } } }); }
/// <summary> /// Creates a question loader for a generated question. /// </summary> IQuestionResolver IQuestionResultVisitor <IQuestionResolver, UserQuestionData> .Visit( GeneratedQuestionTemplate question, UserQuestionData userQuestionData) { return(new GeneratedQuestionTemplateResolver ( userQuestionData, _jsonSerializer )); }
/// <summary> /// Returns a mock question generator. /// </summary> private Mock <IQuestionGenerator> GetMockQuestionGenerator( GeneratedQuestionTemplate question, QuestionGenerationResult generationResult = null) { var questionGenerator = new Mock <IQuestionGenerator>(); questionGenerator .Setup(qg => qg.GenerateQuestionAsync(question, 0 /*seed*/)) .ReturnsAsync(generationResult); return(questionGenerator); }
/// <summary> /// Creates a question updater for a generated question. /// </summary> IQuestionUpdater IQuestionResultVisitor <IQuestionUpdater, IModelErrorCollection> .Visit( GeneratedQuestionTemplate question, IModelErrorCollection errors) { return(new GeneratedQuestionUpdater ( _dbContext, question, errors, _questionGenerator, _timeProvider )); }
/// <summary> /// Returns a list of classes to import. /// </summary> private List <string> GetClassesToImport(GeneratedQuestionTemplate question) { return(new List <string>() { "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.databind.JsonMappingException" }.Concat ( question.ImportedClasses?.Select ( importedClass => importedClass.ClassName ) ?? Enumerable.Empty <string>() ).ToList()); }
/// <summary> /// Generates a specific question given a generated question and a seed. /// </summary> public async Task<QuestionGenerationResult> GenerateQuestionAsync( GeneratedQuestionTemplate question, int seed) { var classJob = CreateQuestionGeneratorClassJob(question, seed); var classJobResult = await _codeRunnerService.ExecuteClassJobAsync(classJob); if (!classJobResult.ClassCompilationResult.Success) { return new QuestionGenerationResult ( error: "Failed to compile question generator. Compilation errors: \n" + string.Join( "\n", classJobResult.ClassCompilationResult.Errors.Select(error => error.FullError)) ); } if (!classJobResult.ClassDefinition.Methods.Any( m => m.IsStatic && m.IsPublic && m.ReturnType.EndsWith("Question") && m.Name == "generateQuestion" && m.ParameterTypes.Count == 1 && m.ParameterTypes.Single() == "int")) { return new QuestionGenerationResult ( error: "The QuestionGenerator class must have a method with the following signature: \n" + "public static Question generateQuestion(int seed)" ); } if (!classJobResult.TestsCompilationResult.Success) { return new QuestionGenerationResult ( error: "Failed to compile question generator. Compilation errors: \n" + string.Join( "\n", classJobResult.TestsCompilationResult.Errors.Select(error => error.FullError)) ); } if (!classJobResult.TestResults[0].Completed) { return new QuestionGenerationResult ( error: "An exception was encountered when calling QuestionGenerator.generateQuestion: \n" + classJobResult.TestResults[0].Exception ); } return new QuestionGenerationResult ( classJobResult.TestResults[0].ReturnValue, fullGeneratorFileContents: classJob.FileContents, fullGeneratorFileLineOffset: classJob.LineNumberOffset ); }
/// <summary> /// Returns a list of classes to import. /// </summary> private List<string> GetClassesToImport(GeneratedQuestionTemplate question) { return new List<string>() { "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.databind.JsonMappingException" }.Concat ( question.ImportedClasses?.Select ( importedClass => importedClass.ClassName ) ?? Enumerable.Empty<string>() ).ToList(); }
/// <summary> /// Creates a class job that will generate the question, and return the result /// in the form of a json string. /// </summary> private ClassJob CreateQuestionGeneratorClassJob( GeneratedQuestionTemplate question, int seed) { var classesToImport = GetClassesToImport(question); int lineOffset = question.FullGeneratorFileLineOffset; return new ClassJob() { ClassesToImport = classesToImport, ClassName = "QuestionGenerator", FileContents = !string.IsNullOrEmpty(question.FullGeneratorFileContents) ? question.FullGeneratorFileContents : GetGeneratorFile(classesToImport.Count, question.GeneratorContents, out lineOffset), LineNumberOffset = lineOffset, Tests = new List<ClassTest>() { new ClassTest() { TestName = "QuestionGenerator", MethodBody = string.Join( "\n", new List<string>() { "ObjectMapper mapper = new ObjectMapper();", $"try", "{{", $"\treturn mapper.writeValueAsString(QuestionGenerator.generateQuestion({seed}));", "}}", "catch (Exception ex)", "{{", "\tthrow new RuntimeException(ex);", "}}" }), ReturnType = "String" } } }; }
/// <summary> /// Creates a question loader for a generated question. /// </summary> IQuestionLoader IQuestionResultVisitor <IQuestionLoader> .Visit(GeneratedQuestionTemplate question) { return(new GeneratedQuestionLoader(_dbContext, question)); }
/// <summary> /// Regenerates the question. /// </summary> private async Task RegenerateQuestionAsync( GeneratedQuestionTemplate generatedQuestionTemplate, int seed, UserQuestionData userQuestionData) { var result = await _questionGenerator.GenerateQuestionAsync ( generatedQuestionTemplate, seed ); userQuestionData.Seed = seed; userQuestionData.CachedQuestionData = result.SerializedQuestion; userQuestionData.CachedQuestionDataTime = _timeProvider.UtcNow; }
/// <summary> /// Returns a mock question generator. /// </summary> private Mock<IQuestionGenerator> GetMockQuestionGenerator( GeneratedQuestionTemplate question, QuestionGenerationResult generationResult) { var questionGenerator = new Mock<IQuestionGenerator>(); questionGenerator .Setup(qg => qg.GenerateQuestionAsync(question, 0 /*seed*/)) .ReturnsAsync(generationResult); return questionGenerator; }
/// <summary> /// Creates a question loader for a generated question. /// </summary> IUserQuestionDataUpdater IQuestionResultVisitor <IUserQuestionDataUpdater> .Visit( GeneratedQuestionTemplate question) { return(_generatedUserQuestionDataUpdater); }
/// <summary> /// Creates a question duplicator for a generated question. /// </summary> IQuestionDuplicator IQuestionResultVisitor <IQuestionDuplicator> .Visit(GeneratedQuestionTemplate question) { return(new GeneratedQuestionDuplicator(_dbContext, question)); }
/// <summary> /// Generates a specific question given a generated question and a seed. /// </summary> public async Task <QuestionGenerationResult> GenerateQuestionAsync( GeneratedQuestionTemplate question, int seed) { var classJob = CreateQuestionGeneratorClassJob(question, seed); var classJobResult = await _codeRunnerService.ExecuteClassJobAsync(classJob); if (!classJobResult.ClassCompilationResult.Success) { return(new QuestionGenerationResult ( error: "Failed to compile question generator. Compilation errors: \n" + string.Join( "\n", classJobResult.ClassCompilationResult.Errors.Select(error => error.FullError)) )); } if (!classJobResult.ClassDefinition.Methods.Any( m => m.IsStatic && m.IsPublic && m.ReturnType.EndsWith("Question") && m.Name == "generateQuestion" && m.ParameterTypes.Count == 1 && m.ParameterTypes.Single() == "int")) { return(new QuestionGenerationResult ( error: "The QuestionGenerator class must have a method with the following signature: \n" + "public static Question generateQuestion(int seed)" )); } if (!classJobResult.TestsCompilationResult.Success) { return(new QuestionGenerationResult ( error: "Failed to compile question generator. Compilation errors: \n" + string.Join( "\n", classJobResult.TestsCompilationResult.Errors.Select(error => error.FullError)) )); } if (!classJobResult.TestResults[0].Completed) { return(new QuestionGenerationResult ( error: "An exception was encountered when calling QuestionGenerator.generateQuestion: \n" + classJobResult.TestResults[0].Exception )); } return(new QuestionGenerationResult ( classJobResult.TestResults[0].ReturnValue, classJob.FileContents, classJob.LineNumberOffset, seed )); }
/// <summary> /// Creates a question loader for a generated question. /// </summary> IQuestionGrader IQuestionResultVisitor <IQuestionGrader> .Visit(GeneratedQuestionTemplate question) { throw new InvalidOperationException("Generated questions cannot be graded directly. Instead, grade the question with a particular seed."); }