package nl.uva.sc.encoders.ql.validation; import static nl.uva.sc.encoders.ql.ast.ConditionalBlockBuilder.aConditionalBlock; import static nl.uva.sc.encoders.ql.ast.QuestionBuilder.aQuestion; import static nl.uva.sc.encoders.ql.ast.QuestionnaireBuilder.aQuestionnaire; import static nl.uva.sc.encoders.ql.ast.TextLocationBuilder.aTextLocation; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import nl.uva.sc.encoders.ql.ast.Questionnaire; import nl.uva.sc.encoders.ql.ast.expression.BinaryExpression; import nl.uva.sc.encoders.ql.ast.expression.BracedExpression; import nl.uva.sc.encoders.ql.ast.expression.Expression; import nl.uva.sc.encoders.ql.ast.expression.LiteralExpression; import nl.uva.sc.encoders.ql.ast.expression.NameExpression; import nl.uva.sc.encoders.ql.ast.expression.UnaryExpression; import nl.uva.sc.encoders.ql.ast.literal.BooleanLiteral; import nl.uva.sc.encoders.ql.ast.literal.IntegerLiteral; import nl.uva.sc.encoders.ql.ast.operator.AddOperator; import nl.uva.sc.encoders.ql.ast.operator.AndOperator; import nl.uva.sc.encoders.ql.ast.operator.GreaterThanOperator; import nl.uva.sc.encoders.ql.ast.operator.NotOperator; import nl.uva.sc.encoders.ql.ast.statement.ConditionalBlock; import nl.uva.sc.encoders.ql.ast.statement.Question; import nl.uva.sc.encoders.ql.ast.statement.Statement; import nl.uva.sc.encoders.ql.ast.type.BooleanType; import nl.uva.sc.encoders.ql.ast.type.IntegerType; import nl.uva.sc.encoders.ql.ast.type.StringType; import org.junit.Test; public class TypeCheckerTest { @Test public void testCheckTypes_conditionsWithBooleansAreAllowed() { Expression leftHand = new LiteralExpression(aTextLocation().build(), new BooleanLiteral(true)); Expression rightHand = new LiteralExpression(aTextLocation().build(), new BooleanLiteral(true)); Expression condition = new BinaryExpression(aTextLocation().build(), leftHand, rightHand, new AndOperator("&&")); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_conditionsWithIntegersAreNotAllowed() { Expression leftHand = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(0)); Expression rightHand = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(1)); Expression condition = new BinaryExpression(aTextLocation().build(), leftHand, rightHand, new AddOperator("+")); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); ValidationMessage validationMessage = validations.get(0); assertThat(validationMessage.getValidationMessage(), is("Condition has to be of type boolean. Type encountered is 'integer'")); } @Test public void testCheckTypes_duplicateLabelsAreNotAllowed() { String questionLabel = "What is the meaning of life?"; Question questionA = aQuestion().withQuestionLabel(questionLabel).build(); Question questionB = aQuestion().withQuestionLabel(questionLabel).build(); List<Question> questions = Arrays.asList(questionA, questionB); Questionnaire questionnaire = aQuestionnaire().withQuestions(questions).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); ValidationMessage validationMessage = validations.get(0); assertThat(validationMessage.getValidationMessage(), is("Duplicate label 'What is the meaning of life?'")); } @Test public void testCheckTypes_differentLabelsAreAllowed() { String questionLabel = "What is the meaning of life?"; Question questionA = aQuestion().withQuestionLabel(questionLabel).build(); Question questionB = aQuestion().withQuestionLabel(questionLabel + "2").build(); List<Question> questions = Arrays.asList(questionA, questionB); Questionnaire questionnaire = aQuestionnaire().withQuestions(questions).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_questionThatIsReferencedBeforeItIsListedIsInvalid() { String questionName = "lateQuestion"; Question question = aQuestion().withName(questionName).withQuestionLabel("Ask me later").withDataType(new BooleanType()) .build(); List<Statement> statements = new ArrayList<>(); Expression condition = new NameExpression(aTextLocation().build(), questionName); // We add the condition before the question statements.add(aConditionalBlock().withCondition(condition).build()); statements.add(question); Questionnaire questionnaire = aQuestionnaire().withStatements(statements).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Reference may only be listed after the question it references. Question: lateQuestion")); } @Test public void testCheckTypes_questionThatIsReferencedAfterItIsListedIsValid() { String questionName = "onTimeQuestion"; Question question = aQuestion().withName(questionName).withQuestionLabel("Ask me now").withDataType(new BooleanType()) .build(); List<Statement> statements = new ArrayList<>(); Expression condition = new NameExpression(aTextLocation().build(), questionName); // We add the condition after the question statements.add(question); statements.add(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withStatements(statements).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_questionThatIsUndefinedIsValid() { String questionName = "notExistingQuestion"; List<Statement> statements = new ArrayList<>(); Expression condition = new NameExpression(aTextLocation().build(), questionName); statements.add(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withStatements(statements).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Reference to undefined question 'notExistingQuestion'")); } @Test public void testCheckTypes_questionWithBracedExpressionCanHaveValidations() { String existingQuestionName = "existingQuestion"; Question question = aQuestion().withName(existingQuestionName).withQuestionLabel("Ask me now") .withDataType(new BooleanType()).build(); String notExistingQuestionName = "notExistingQuestion"; List<Statement> statements = new ArrayList<>(); Expression leftHand = new NameExpression(aTextLocation().build(), existingQuestionName); Expression rightHand = new NameExpression(aTextLocation().build(), notExistingQuestionName); Expression binaryExpression = new BinaryExpression(aTextLocation().build(), leftHand, rightHand, new AndOperator("&&")); Expression condition = new BracedExpression(aTextLocation().build(), binaryExpression); statements.add(question); statements.add(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withStatements(statements).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Reference to undefined question 'notExistingQuestion'")); } @Test public void testCheckTypes_notOperatorWithIntegerIsNotValid() { Expression expression = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(1)); Expression condition = new UnaryExpression(aTextLocation().build(), new NotOperator("!"), expression); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Operator '!' does not support operations with datatype integer")); } @Test public void testCheckTypes_notOperatorWithBooleanIsValid() { Expression expression = new LiteralExpression(aTextLocation().build(), new BooleanLiteral(true)); Expression condition = new UnaryExpression(aTextLocation().build(), new NotOperator("!"), expression); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_greaterThanOperatorWithIntegerIsValid() { Expression leftHand = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(3)); Expression rightHand = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(2)); Expression condition = new BinaryExpression(aTextLocation().build(), leftHand, rightHand, new GreaterThanOperator(">")); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_greaterThanOperatorWithBooleanIsInValid() { Expression leftHand = new LiteralExpression(aTextLocation().build(), new BooleanLiteral(true)); Expression rightHand = new LiteralExpression(aTextLocation().build(), new BooleanLiteral(false)); Expression condition = new BinaryExpression(aTextLocation().build(), leftHand, rightHand, new GreaterThanOperator(">")); List<ConditionalBlock> conditionalBlocks = Arrays.asList(aConditionalBlock().withCondition(condition).build()); Questionnaire questionnaire = aQuestionnaire().withConditionalBlocks(conditionalBlocks).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Operator '>' does not support operations with lefthand datatype boolean, righthand datatype boolean")); } @Test public void testCheckTypes_computedValueWithDifferentDataTypeThanQuestionIsInvalid() { Expression computed = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(5)); Question question = aQuestion().withDataType(new StringType()).withComputed(computed).build(); List<Question> questions = Arrays.asList(question); Questionnaire questionnaire = aQuestionnaire().withQuestions(questions).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Computed type integer does not match question type string")); } @Test public void testCheckTypes_computedValueWithSameDataTypeAsQuestionIsValid() { Expression computed = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(5)); Question question = aQuestion().withDataType(new IntegerType()).withComputed(computed).build(); List<Question> questions = Arrays.asList(question); Questionnaire questionnaire = aQuestionnaire().withQuestions(questions).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(0)); } @Test public void testCheckTypes_computedValueValidationsAreChecked() { Expression literalExpression = new LiteralExpression(aTextLocation().build(), new IntegerLiteral(5)); Expression computed = new UnaryExpression(aTextLocation().build(), new NotOperator("!"), literalExpression); Question question = aQuestion().withDataType(new IntegerType()).withComputed(computed).build(); List<Question> questions = Arrays.asList(question); Questionnaire questionnaire = aQuestionnaire().withQuestions(questions).build(); TypeChecker typeChecker = new TypeChecker(questionnaire); List<TypeValidation> validations = typeChecker.checkTypes(); assertThat(validations.toString(), validations.size(), is(1)); TypeValidation typeValidation = validations.get(0); assertThat(typeValidation.getValidationMessage(), is("Operator '!' does not support operations with datatype integer")); } }