Skip to content

Commit

Permalink
Add unit tests for CommandLogic and its components
Browse files Browse the repository at this point in the history
  • Loading branch information
limhawjia committed Oct 22, 2019
1 parent 8d45e07 commit 336ea01
Show file tree
Hide file tree
Showing 23 changed files with 254 additions and 60 deletions.
34 changes: 26 additions & 8 deletions src/main/java/com/dukeacademy/MainApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
import com.dukeacademy.commons.exceptions.DataConversionException;
import com.dukeacademy.commons.util.ConfigUtil;
import com.dukeacademy.commons.util.StringUtil;
import com.dukeacademy.logic.commands.CommandLogic;
import com.dukeacademy.logic.commands.CommandLogicManager;
import com.dukeacademy.logic.program.ProgramSubmissionLogic;
import com.dukeacademy.logic.program.ProgramSubmissionLogicManager;
import com.dukeacademy.logic.program.exceptions.LogicCreationException;
import com.dukeacademy.logic.question.QuestionsLogic;
import com.dukeacademy.logic.question.QuestionsLogicManager;
import com.dukeacademy.model.prefs.ReadOnlyUserPrefs;
import com.dukeacademy.model.prefs.UserPrefs;
Expand All @@ -25,8 +28,8 @@
import com.dukeacademy.storage.question.JsonQuestionBankStorage;
import com.dukeacademy.storage.question.QuestionBankStorage;
import com.dukeacademy.ui.Ui;

import com.dukeacademy.ui.UiManager;

import javafx.application.Application;
import javafx.stage.Stage;

Expand Down Expand Up @@ -63,18 +66,27 @@ public void init() throws Exception {

initLogging(config);

commandLogic = this.initCommandLogic();
questionsLogic = this.initQuestionsLogic(userPrefs);
programSubmissionLogic = this.initProgramSubmissionLogic(userPrefs);
commandLogic = this.initCommandLogic(userPrefs);
ui = this.initUi(userPrefs);
programSubmissionLogic = this.initProgramSubmissionLogic();
ui = this.initUi(commandLogic, questionsLogic, programSubmissionLogic, userPrefs);
}

/**
* Returns a new QuestionLogicManager based on the UserPrefs passed into the function.
* @param userPrefs a UserPrefs instance.
* @return a QuestionsLogicManager instance.
*/
private QuestionsLogicManager initQuestionsLogic(ReadOnlyUserPrefs userPrefs) {
QuestionBankStorage storage = new JsonQuestionBankStorage(userPrefs.getQuestionBankFilePath());
return new QuestionsLogicManager(storage);
}

private ProgramSubmissionLogicManager initProgramSubmissionLogic(ReadOnlyUserPrefs userPrefs) {
/**
* Returns a new ProgramSubmissionLogicManager based on the UserPrefs passed into the function.
* @return a ProgramSubmissionLogicManager instance.
*/
private ProgramSubmissionLogicManager initProgramSubmissionLogic() {
// TODO: eventually store program execution path in user prefs
String outputPath = System.getProperty("user.home") + File.separator + "DukeAcademy";
File file = new File(outputPath);
Expand All @@ -87,17 +99,23 @@ private ProgramSubmissionLogicManager initProgramSubmissionLogic(ReadOnlyUserPre
} catch (LogicCreationException e) {
logger.info("Fatal: failed to create program submission logic. Exiting app.");
this.stop();
throw new RuntimeException();
return null;
}
}

private CommandLogicManager initCommandLogic(ReadOnlyUserPrefs userPrefs) {
/**
* Returns a new CommandLogicManager based on the UserPrefs passed into the function. Any commands to be registered
* in the CommandLogicManager is done in this method.
* @return a CommandLogicManger instance.
*/
private CommandLogicManager initCommandLogic() {
CommandLogicManager commandLogicManager = new CommandLogicManager();
// TODO: create and register commands
return commandLogicManager;
}

private Ui initUi(ReadOnlyUserPrefs userPrefs) {
private Ui initUi(CommandLogic commandLogic, QuestionsLogic questionsLogic,
ProgramSubmissionLogic programSubmissionLogic, ReadOnlyUserPrefs userPrefs) {
return new UiManager(commandLogic, questionsLogic, programSubmissionLogic, userPrefs.getGuiSettings());
}

Expand Down
32 changes: 23 additions & 9 deletions src/main/java/com/dukeacademy/commons/util/StringUtil.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.dukeacademy.commons.util;

import com.dukeacademy.commons.exceptions.IllegalValueException;

import static com.dukeacademy.commons.util.AppUtil.checkArgument;

import static java.util.Objects.requireNonNull;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Optional;

import com.dukeacademy.commons.exceptions.IllegalValueException;

/**
* Helper functions for handling strings.
Expand Down Expand Up @@ -71,22 +69,38 @@ public static boolean isNonZeroUnsignedInteger(String s) {
}
}

/**
* Returns the first word of a string.
* @param s the string to be processed.
* @return the first word of the string.
* @throws IllegalValueException if the string does not contain a valid word.
*/
public static String getFirstWord(String s) throws IllegalValueException {
if (!s.matches(VALID_WORD_REGEX)) {
String stripped = s.stripLeading();

if (!stripped.matches(VALID_WORD_REGEX)) {
throw new IllegalValueException("String is not a valid word.");
}
return s.split(" ", 2)[0].trim();
return stripped.split("[\\s]+", 2)[0].trim();
}

/**
* Returns the a new string with the first word removed.
* @param s the string to be processed.
* @return the remainder of the string after the first word is removed.
* @throws IllegalValueException if the string does not contain a valid word.
*/
public static String removeFirstWord(String s) throws IllegalValueException {
if (!s.matches(VALID_WORD_REGEX)) {
String stripped = s.stripLeading();

if (!stripped.matches(VALID_WORD_REGEX)) {
throw new IllegalValueException("String is not a valid word.");
}

if (s.split("\\s", 2).length == 1) {
if (stripped.split("[\\s]+", 2).length == 1) {
return "";
} else {
return s.split("\\s", 2)[1].stripLeading();
return stripped.split("[\\s]+", 2)[1].stripLeading();
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/dukeacademy/logic/commands/CommandLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
import com.dukeacademy.logic.commands.exceptions.CommandException;
import com.dukeacademy.logic.parser.exceptions.ParseException;

/**
* Provides all the necessary methods needed to execute commands in the application.
*/
public interface CommandLogic {
/**
* Executes a command based on the command text that is provided.
* @param commandText the command text that should contain the command keyword and the necessary arguments.
* @return a command result instance.
* @throws ParseException if the command text provided is invalid.
* @throws CommandException if the execution of the command fails.
*/
CommandResult executeCommand(String commandText) throws ParseException, CommandException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,28 @@
import com.dukeacademy.logic.commands.exceptions.CommandException;
import com.dukeacademy.logic.parser.exceptions.ParseException;

/**
* Implementation of the CommandLogic interface. This implementation uses a helper class, CommandParser to help
* parse its commands. Any commands to be executed by this CommandLogic implementation has to be registered through
* the registerCommand method.
*/
public class CommandLogicManager implements CommandLogic {
private CommandParser commandParser;

public CommandLogicManager() {
this.commandParser = new CommandParser();
}

/**
* Registers a command that will now be executed by this CommandLogic instance.
* @param commandWord the keyword of the command.
* @param commandSupplier a supplier of command instances.
*/
public void registerCommand(String commandWord, CommandSupplier commandSupplier) {
this.commandParser.registerCommandFactory(commandWord, commandSupplier);
this.commandParser.registerCommand(commandWord, commandSupplier);
}

@Override
public CommandResult executeCommand(String commandText) throws ParseException, CommandException {
Command command = this.commandParser.parseCommandText(commandText);
return command.execute();
Expand Down
24 changes: 19 additions & 5 deletions src/main/java/com/dukeacademy/logic/commands/CommandParser.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
package com.dukeacademy.logic.commands;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.dukeacademy.commons.exceptions.IllegalValueException;
import com.dukeacademy.commons.util.StringUtil;
import com.dukeacademy.logic.commands.exceptions.InvalidCommandArgumentsException;
import com.dukeacademy.logic.parser.exceptions.ParseException;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
* Helper class used by CommandLogicManager to keep track and instantiate registered Command classes.
*/
public class CommandParser {
private Map<String, CommandSupplier> commandFactoryMap;

public CommandParser() {
this.commandFactoryMap = new HashMap<>();
}

public void registerCommandFactory(String commandWord, CommandSupplier commandSupplier) {
/**
* Registers a command into the command parser for future access.
* @param commandWord the keyword of the command.
* @param commandSupplier the supplier of the command.
*/
public void registerCommand(String commandWord, CommandSupplier commandSupplier) {
this.commandFactoryMap.put(commandWord, commandSupplier);
}

/**
* Instantiates and returns a Command class instance based on the command text provided.
* @param commandText the command text used to invoke the command.
* @return a command instance corresponding to the command text.
* @throws ParseException if the command keyword or arguments are invalid.
*/
public Command parseCommandText(String commandText) throws ParseException {
try {
String commandWord = StringUtil.getFirstWord(commandText);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import com.dukeacademy.logic.commands.exceptions.InvalidCommandArgumentsException;

import java.util.Optional;

/**
* Functional interface to represent a command supplier that accepts arguments for the command to act on.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.dukeacademy.logic.commands.exceptions;

public class InvalidCommandArgumentsException extends Exception {
/**
* Exception thrown when the arguments provided for a command is invalid.
*/
public class InvalidCommandArgumentsException extends Exception {
public InvalidCommandArgumentsException(String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dukeacademy.logic.commands.factory;

import com.dukeacademy.logic.commands.Command;
import com.dukeacademy.logic.commands.exceptions.InvalidCommandArgumentsException;

/**
* Encapsulates the creation of commands and its dependencies and exposes an interface that can be injected into
Expand All @@ -18,5 +19,5 @@ public interface CommandFactory {
* @param commandArguments the command text from the user's input.
* @return the corresponding command class instance.
*/
Command getCommand(String commandArguments);
Command getCommand(String commandArguments) throws InvalidCommandArgumentsException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
* Functional interface used by ProgramSubmissionLogicManagers to allow external components to submit user programs.
*/
public interface ProgramSubmissionChannel {
public void submitProgram(UserProgram program);
public UserProgram getProgram();
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,17 @@ public interface ProgramSubmissionLogic {
* @return True if the program was successfully tested.
*/
public boolean submitUserProgram(UserProgram userProgram);

/**
* Sets a channel which allows the logic instance to retrieve user programs for submission.
* @param channel The channel to be set.
*/
public void setUserProgramSubmissionChannel(ProgramSubmissionChannel channel);

/**
* Retrieves a user program from the submission channel and tests it against the current question being handled
* by the logic instance.
* @return True if the program was successfully tested.
*/
public boolean submitUserProgramFromSubmissionChannel();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.dukeacademy.commons.core.LogsCenter;
import com.dukeacademy.logic.program.exceptions.LogicCreationException;
import com.dukeacademy.logic.program.exceptions.NoQuestionSetException;
import com.dukeacademy.logic.program.exceptions.SubmissionChannelNotSetException;
import com.dukeacademy.logic.program.exceptions.SubmissionLogicManagerClosedException;
import com.dukeacademy.model.program.TestResult;
import com.dukeacademy.model.question.Question;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class ProgramSubmissionLogicManager implements ProgramSubmissionLogic {
private CompilerEnvironment compilerEnvironment;
private TestExecutor testExecutor;
private boolean isClosed;
private ProgramSubmissionChannel submissionChannel;

/**
* Constructor.
Expand Down Expand Up @@ -116,6 +118,21 @@ public boolean submitUserProgram(UserProgram userProgram) {
return true;
}

@Override
public void setUserProgramSubmissionChannel(ProgramSubmissionChannel channel) {
this.submissionChannel = channel;
}

@Override
public boolean submitUserProgramFromSubmissionChannel() {
if (this.submissionChannel == null) {
throw new SubmissionChannelNotSetException();
}

UserProgram program = this.submissionChannel.getProgram();
return this.submitUserProgram(program);
}

private void verifyNotClosed() {
if (this.isClosed) {
throw new SubmissionLogicManagerClosedException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dukeacademy.logic.program.exceptions;

/**
* Exception thrown when the ProgramSubmissionLogic instance does not have a submission channel set and attempts
* to submit a program through the submission channel.
*/
public class SubmissionChannelNotSetException extends RuntimeException {
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public String getLocationPath() {
private void createDirectory(Path path) throws CompilerEnvironmentException {
try {
String directoryPath = path.toUri().getPath();
if (!new File(directoryPath).mkdir()) {
if (!new File(directoryPath).exists() && !new File(directoryPath).mkdir()) {
throw new CompilerEnvironmentException(messageCreateEnvironmentFailed);
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/dukeacademy/ui/Editor.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.FileWriter;
import java.io.IOException;

import com.dukeacademy.model.question.UserProgram;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
Expand Down Expand Up @@ -73,4 +75,12 @@ public String onSubmitButtonClick(ActionEvent e) {
System.out.println(textOutput.getText().strip());
return textOutput.getText().strip();
}

/**
* Returns the current text in the editor.
* @return current text in editor.
*/
public UserProgram getUserProgram() {
return new UserProgram("Main", textOutput.getText().strip());
}
}
Loading

0 comments on commit 336ea01

Please sign in to comment.