Browse Source

basic command system, unit tests for parser

Kajetan Johannes Hammerle 4 years ago
parent
commit
45d4c1ef80

+ 83 - 2
MainTest.cpp

@@ -1,6 +1,7 @@
 #include <iostream>
 #include "data/UnsortedArrayList.h"
 #include "data/HashMap.h"
+#include "server/CommandUtils.h"
 
 using namespace std;
 
@@ -46,7 +47,19 @@ void checkEqual(int a, int b, string text)
     }
     else
     {
-        notifyFail(text + " - expected " + std::to_string(a) + " got " + std::to_string(b));
+        notifyFail(text + " - expected '" + std::to_string(a) + "' got '" + std::to_string(b) + "'");
+    }
+}
+
+void checkEqual(string a, string b, string text)
+{
+    if(a == b)
+    {
+        notifySuccess(text);
+    }
+    else
+    {
+        notifyFail(text + " - expected '" + a + "' got '" + b + "'");
     }
 }
 
@@ -58,7 +71,7 @@ void checkBool(bool a, bool b, string text)
     }
     else
     {
-        notifyFail(text + " - expected " + std::to_string(a) + " got " + std::to_string(b));
+        notifyFail(text + " - expected '" + std::to_string(a) + "' got '" + std::to_string(b) + "'");
     }
 }
 
@@ -378,6 +391,73 @@ void testHashMap()
     finalizeTest();
 }
 
+// -----------------------------------------------------------------------------
+// tests of command parser
+// -----------------------------------------------------------------------------
+
+void checkCommandParser(string rawCommand, string command, vector<string>& args, string text)
+{
+    string parsedCommand;
+    vector<string> parsedArgs;
+    
+    CommandUtils::splitString(rawCommand, parsedCommand, parsedArgs);
+    
+    checkEqual(command, parsedCommand, text);
+    checkEqual(args.size(), parsedArgs.size(), text);
+    
+    for(int i = 0; i < args.size() && i < parsedArgs.size(); i++)
+    {
+        checkEqual(args[i], parsedArgs[i], text);
+    }
+}
+
+void testCommandParser()
+{
+    vector<string> args;
+    
+    args = {};
+    checkCommandParser("test", "test", args, "command without arguments");
+    
+    args = {"aaa"};
+    checkCommandParser("test aaa", "test", args, "command with one argument");
+    
+    args = {"aaa", "bbbb"};
+    checkCommandParser("test aaa bbbb", "test", args, "command with two arguments");
+    
+    args = {"aaa", "bbbb", "ccccc"};
+    checkCommandParser("test aaa bbbb ccccc", "test", args, "command with three arguments");
+    
+    args = {};
+    checkCommandParser("test    ", "test", args, "command with spaces");
+    
+    args = {"aaa", "bbbb"};
+    checkCommandParser("test  aaa   bbbb", "test", args, "command with arguments and spaces");
+    
+    args = {"aaa", "bbbb"};
+    checkCommandParser("test  aaa   bbbb", "test", args, "command with arguments and spaces");
+    
+    args = {"aaa bbbb"};
+    checkCommandParser("test \"aaa bbbb\"", "test", args, "command with one argument and quotation marks");
+    
+    args = {"aaa bbbb", "ccc"};
+    checkCommandParser("test \"aaa bbbb\" ccc", "test", args, "command with two arguments and quotation marks");
+    
+    args = {"ddd", "aaa bbbb", "ccc"};
+    checkCommandParser("test ddd \"aaa bbbb\" ccc", "test", args, "command with tree arguments and quotation marks");
+    
+    args = {};
+    checkCommandParser("test \"", "", args, "command syntax exception 1");
+    
+    args = {};
+    checkCommandParser("test aaa\"", "", args, "command syntax exception 2");
+    
+    args = {};
+    checkCommandParser("test aaa\"bbb\"", "", args, "command syntax exception 3");
+    
+    args = {};
+    checkCommandParser("test aaa \"bbb\"ccc", "", args, "command syntax exception 4");
+}
+
 // -----------------------------------------------------------------------------
 // main
 // -----------------------------------------------------------------------------
@@ -386,6 +466,7 @@ int main(int argc, char** argv)
 {
     testUnsortedArrayList();
     testHashMap();
+    testCommandParser();
     return 0;
 }
 

+ 28 - 5
Makefile

@@ -202,12 +202,35 @@ Camera3D.o: math/Camera3D.h math/Camera3D.cpp\
 run_server: game_server
 	./game_server
 	
-game_server: MainServer.cpp GameServer.o Stream.o Server.o
-	g++ $(VERSION) -o $@ MainServer.cpp GameServer.o Stream.o Server.o -lpthread
+game_server: MainServer.cpp GameServer.o Stream.o Server.o CommandManager.o\
+	BaseCommand.o TestCommand.o CommandUtils.o
+	g++ $(VERSION) -o $@ MainServer.cpp GameServer.o Stream.o Server.o\
+	    CommandManager.o BaseCommand.o TestCommand.o CommandUtils.o -lpthread
 	
-GameServer.o: server/GameServer.h server/GameServer.cpp network/server/Server.h
+GameServer.o: server/GameServer.h server/GameServer.cpp network/server/Server.h\
+	server/CommandManager.h server/ICommandSource.h
 	g++ $(VERSION) -c server/GameServer.cpp -o $@
 	
+CommandManager.o: server/CommandManager.h server/CommandManager.cpp\
+	server/GameServer.h server/CommandUtils.h\
+	server/commands/BaseCommand.h server/commands/TestCommand.h 
+	g++ $(VERSION) -c server/CommandManager.cpp -o $@
+	
+CommandUtils.o: server/CommandUtils.h server/CommandUtils.cpp
+	g++ $(VERSION) -c server/CommandUtils.cpp -o $@
+	
+# ------------------------------------------------------------------------------	
+# commands
+# ------------------------------------------------------------------------------
+	
+BaseCommand.o: server/commands/BaseCommand.h server/commands/BaseCommand.cpp\
+	server/GameServer.h server/ICommandSource.h
+	g++ $(VERSION) -c server/commands/BaseCommand.cpp -o $@
+	
+TestCommand.o: server/commands/TestCommand.h server/commands/TestCommand.cpp\
+	server/commands/BaseCommand.h
+	g++ $(VERSION) -c server/commands/TestCommand.cpp -o $@
+	
 # ------------------------------------------------------------------------------	
 # network
 # ------------------------------------------------------------------------------
@@ -226,8 +249,8 @@ Stream.o: network/stream/Stream.h network/stream/Stream.cpp
 run_tests: test
 	./test
 	
-test: MainTest.cpp data/UnsortedArrayList.h data/HashMap.h
-	g++ $(VERSION) -o $@ MainTest.cpp
+test: MainTest.cpp data/UnsortedArrayList.h data/HashMap.h CommandUtils.o
+	g++ $(VERSION) -o $@ MainTest.cpp CommandUtils.o
 	
 # ------------------------------------------------------------------------------	
 # clean

+ 36 - 0
server/CommandManager.cpp

@@ -0,0 +1,36 @@
+#include "CommandManager.h"
+#include "commands/TestCommand.h"
+#include "CommandUtils.h"
+
+CommandManager::CommandManager()
+{
+    registerCommand(new TestCommand());
+}
+
+CommandManager::~CommandManager()
+{
+}
+
+void CommandManager::registerCommand(BaseCommand* command)
+{
+    commands.insert(pair<string, unique_ptr<BaseCommand>> (command->getName(), command));
+}
+
+void CommandManager::execute(ICommandSource& cs, const string& rawCommand) const
+{
+    vector<string> args;
+    
+    string command;
+    if(CommandUtils::splitString(rawCommand, command, args))
+    {
+        cout << "Invalid command syntax: '" << rawCommand << "'" << endl;
+        return;
+    }
+
+    if(commands.find(command) == commands.end())
+    {
+        cout << "Unknown command: '" << command << "'" << endl;
+        return;
+    }
+    commands.begin()->second->execute(cs, args);
+}

+ 26 - 0
server/CommandManager.h

@@ -0,0 +1,26 @@
+#ifndef COMMANDMANAGER_H
+#define COMMANDMANAGER_H
+
+#include <iostream>
+#include <unordered_map>
+#include "commands/BaseCommand.h"
+#include <memory>
+
+using namespace std;
+
+class CommandManager
+{
+public:
+    CommandManager();
+    virtual ~CommandManager();
+    
+    void execute(ICommandSource& cs, const string& rawCommand) const;
+
+private:
+    unordered_map<string, unique_ptr<BaseCommand>> commands;
+    
+    void registerCommand(BaseCommand* command);
+};
+
+#endif
+

+ 88 - 0
server/CommandUtils.cpp

@@ -0,0 +1,88 @@
+#include "CommandUtils.h"
+#include <iostream>
+
+bool CommandUtils::splitString(const string& rawCommand, string& command, vector<string>& args)
+{
+    bool b = splitStringIntern(rawCommand, command, args);
+    if(b)
+    {
+        args.clear();
+        command = "";
+    }
+    return b;
+}
+
+bool CommandUtils::splitStringIntern(const string& rawCommand, string& command, vector<string>& args)
+{
+    int old = 0;
+    int index = 0;
+    
+    // parse first argument
+    while(index < rawCommand.size())
+    {
+        if(rawCommand[index] == ' ')
+        {
+            command = rawCommand.substr(old, index - old);
+            old = index + 1;
+            index++;
+            break;
+        }
+        index++;
+    }
+    // if the command name is the whole raw command
+    if(index == rawCommand.size())
+    {
+        command = rawCommand.substr(old, index - old);
+        return false;
+    }
+    
+    // parse remainding arguments
+    while(index < rawCommand.size())
+    {
+        if(rawCommand[index] == '"')
+        {
+            // no space before the quotation mark
+            if(index != old) 
+            {
+                return true;
+            }
+            old = index + 1;
+            while(true)
+            {
+                index++;
+                // quotation mark not closed
+                if(index >= rawCommand.size())
+                {
+                    return true;
+                }
+                else if(rawCommand[index] == '"')
+                {
+                    break;
+                }
+            }
+            // no space after quotation mark
+            if(index + 1 < rawCommand.size() && rawCommand[index + 1] != ' ') 
+            {
+                return true;
+            }
+            args.push_back(rawCommand.substr(old, index - old));
+            old = index + 2;
+        }
+        else if(rawCommand[index] == ' ')
+        {
+            if(index - old > 0)
+            {
+                args.push_back(rawCommand.substr(old, index - old));
+            }
+            old = index + 1;
+        }
+        index++;
+    }
+    
+    if(index > old && index - old > 0)
+    {
+        args.push_back(rawCommand.substr(old, index - old));
+    }
+    
+    return false;
+}

+ 15 - 0
server/CommandUtils.h

@@ -0,0 +1,15 @@
+#ifndef COMMANDUTILS_H
+#define COMMANDUTILS_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+namespace CommandUtils
+{
+    bool splitString(const string& rawCommand, string& command, vector<string>& args);
+    static bool splitStringIntern(const string& rawCommand, string& command, vector<string>& args);
+}
+
+#endif

+ 12 - 9
server/GameServer.cpp

@@ -9,30 +9,33 @@ GameServer::~GameServer()
 {
 }
 
+void GameServer::stop()
+{
+    isRunning = false;
+}
+
 void GameServer::start(unsigned short port, unsigned short maxClients)
 {
     std::cout << port << std::endl;
     Server server(port, maxClients);
     server.start(this);
     
-    while(true)
+    isRunning = true;
+    while(isRunning)
     {
-        string in;
-        cin >> in;
-        if(onServerCommand(in))
+        cout << "> ";
+        string command;
+        getline(cin, command, '\n');
+        if(command == "q")
         {
             break;
         }
+        commandManager.execute(*this, command);
     }
     
     server.stop();
 }
 
-bool GameServer::onServerCommand(string& command)
-{
-    return command == "q";
-}
-
 void GameServer::onFullServerClientConnect(int socket)
 {
     string s = "sorry, the server is full";

+ 8 - 2
server/GameServer.h

@@ -3,10 +3,12 @@
 
 #include <iostream>
 #include "../network/server/IServerListener.h"
+#include "ICommandSource.h"
+#include "CommandManager.h"
 
 using namespace std;
 
-class GameServer : public IServerListener
+class GameServer : public IServerListener, public ICommandSource
 {
 public:
     GameServer();
@@ -18,8 +20,12 @@ public:
     void onClientConnect(int socket) override;
     void onClientPackage(int socket, Stream& in) override;
     void onClientDisconnect(int socket) override;
+    
+    void stop();
+    
 private:
-    bool onServerCommand(string& command);
+    bool isRunning = false;
+    CommandManager commandManager;
 };
 
 #endif

+ 10 - 0
server/ICommandSource.h

@@ -0,0 +1,10 @@
+#ifndef ICOMMANDSOURCE_H
+#define ICOMMANDSOURCE_H
+
+class ICommandSource
+{
+public:
+};
+
+#endif
+

+ 14 - 0
server/commands/BaseCommand.cpp

@@ -0,0 +1,14 @@
+#include "BaseCommand.h"
+
+BaseCommand::BaseCommand(string name) : name(name)
+{
+}
+
+BaseCommand::~BaseCommand()
+{
+}
+
+const string& BaseCommand::getName() const
+{
+    return name;
+}

+ 24 - 0
server/commands/BaseCommand.h

@@ -0,0 +1,24 @@
+#ifndef BASECOMMAND_H
+#define BASECOMMAND_H
+
+#include <iostream>
+#include <vector>
+#include "../ICommandSource.h"
+
+using namespace std;
+
+class BaseCommand
+{
+public:
+    BaseCommand(string name);
+    virtual ~BaseCommand();
+    
+    const string& getName() const;
+    virtual void execute(ICommandSource& cs, vector<string>& args) const = 0;
+    
+private:
+    const string name;
+};
+
+#endif
+

+ 20 - 0
server/commands/TestCommand.cpp

@@ -0,0 +1,20 @@
+#include "TestCommand.h"
+
+TestCommand::TestCommand() : BaseCommand("test")
+{
+}
+
+TestCommand::~TestCommand()
+{
+    cout << "destroy test command" << endl;
+}
+
+void TestCommand::execute(ICommandSource& cs, vector<string>& args) const
+{
+    cout << "test command" << endl;
+    for(int i = 0; i < args.size(); i++)
+    {
+        cout << " - " << args[i] << endl;
+    }
+}
+

+ 19 - 0
server/commands/TestCommand.h

@@ -0,0 +1,19 @@
+#ifndef TESTCOMMAND_H
+#define TESTCOMMAND_H
+
+#include "BaseCommand.h"
+
+class TestCommand : public BaseCommand
+{
+public:
+    TestCommand();
+    virtual ~TestCommand();
+    
+    void execute(ICommandSource& cs, vector<string>& args) const;
+    
+private:
+
+};
+
+#endif 
+