1 /*
2  *  SPDX-License-Identifier: GPL-2.0-or-later
3  *
4  *  Copyright (C) 2020-2021  The DOSBox Staging Team
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License along
17  *  with this program; if not, write to the Free Software Foundation, Inc.,
18  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 /* This sample shows how to write a simple unit test for dosbox-staging using
22  * Google C++ testing framework.
23  *
24  * Read Google Test Primer for reference of most available features, macros,
25  * and guidance about writing unit tests:
26  *
27  * https://github.com/google/googletest/blob/master/googletest/docs/primer.md#googletest-primer
28  */
29 
30 /* Include necessary header files; order of headers should be as follows:
31  *
32  * 1. Header declaring functions/classes being tested
33  * 2. <gtest/gtest.h>, which declares the testing framework
34  * 3. Additional system headers (if needed)
35  * 4. Additional dosbox-staging headers (if needed)
36  */
37 
38 #include "shell.h"
39 
40 #include <string>
41 
42 #include <gmock/gmock.h>
43 #include <gtest/gtest.h>
44 
45 #include "dosbox_test_fixture.h"
46 #include "../src/shell/shell_cmds.cpp"
47 
48 namespace {
49 
50 using namespace testing;
51 
52 class DOS_Shell_CMDSTest : public DOSBoxTestFixture {};
53 
54 class MockDOS_Shell : public DOS_Shell {
55 public:
56 	/**
57 	 * NOTE: If we need to call the actual object, we use this. By
58 	 * default, the mocked functions return whatever we tell it to
59 	 * (if given a .WillOnce(Return(...)), or a default value
60 	 * (false).
61 	 *
62 	 * MockDOS_Shell()
63 	 * {
64 	 * 	// delegate call to the real object.
65 	 * 	ON_CALL(*this, execute_shell_cmd)
66 	 * 	        .WillByDefault([this](char *name, char *arguments) {
67 	 * 		        return real_.execute_shell_cmd(name, arguments);
68 	 * 	        });
69 	 * }
70 	 */
71 	MOCK_METHOD(bool, execute_shell_cmd, (char *name, char *arguments), (override));
72 	MOCK_METHOD(void,
73 	            WriteOut,
74 	            (const char *format, const char *arguments),
75 	            (override));
76 
77 private:
78 	DOS_Shell real_; // Keeps an instance of the real in the mock.
79 };
80 
assert_DoCommand(std::string input,std::string expected_name,std::string expected_args)81 void assert_DoCommand(std::string input, std::string expected_name, std::string expected_args)
82 {
83 	MockDOS_Shell shell;
84 	char *input_c_str = const_cast<char *>(input.c_str());
85 	EXPECT_CALL(shell,
86 	            execute_shell_cmd(StrEq(expected_name), StrEq(expected_args)))
87 	        .Times(1)
88 	        .WillOnce(Return(true));
89 	EXPECT_NO_THROW({ shell.DoCommand(input_c_str); });
90 }
91 
92 // Tests chars that separate command name from arguments
TEST_F(DOS_Shell_CMDSTest,DoCommand_Separating_Chars)93 TEST_F(DOS_Shell_CMDSTest, DoCommand_Separating_Chars)
94 {
95 	// These should all cause the parser to stop
96 	std::vector<char> end_chars{
97 	        32,
98 	        '/',
99 	        '\t',
100 	        '=',
101 	};
102 	for (auto end_chr : end_chars) {
103 		MockDOS_Shell shell;
104 		std::string name = "PATH";
105 		std::string args = "";
106 		std::string input = name;
107 		input += end_chr;
108 		input += "ARG";
109 		args += end_chr;
110 		args += "ARG";
111 		assert_DoCommand(input, name, args);
112 	}
113 }
114 
TEST_F(DOS_Shell_CMDSTest,DoCommand_All_Cmds_Do_Valid_Execute)115 TEST_F(DOS_Shell_CMDSTest, DoCommand_All_Cmds_Do_Valid_Execute)
116 {
117 	MockDOS_Shell shell;
118 	for (std::pair<std::string, SHELL_Cmd> cmd : shell_cmds) {
119 		std::string input = cmd.first;
120 		assert_DoCommand(input, input, "");
121 	}
122 }
123 
TEST_F(DOS_Shell_CMDSTest,DoCommand_Trim_Space)124 TEST_F(DOS_Shell_CMDSTest, DoCommand_Trim_Space)
125 {
126 	assert_DoCommand(" PATH ", "PATH", "");
127 }
128 
TEST_F(DOS_Shell_CMDSTest,DoCommand_Splits_Cmd_and_Args)129 TEST_F(DOS_Shell_CMDSTest, DoCommand_Splits_Cmd_and_Args)
130 {
131 	// NOTE: It does not strip the arguments!
132 	assert_DoCommand("DIR *.*", "DIR", " *.*");
133 }
134 
TEST_F(DOS_Shell_CMDSTest,DoCommand_Doesnt_Split_Colon)135 TEST_F(DOS_Shell_CMDSTest, DoCommand_Doesnt_Split_Colon)
136 {
137 	// ensure we don't split on colon ...
138 	assert_DoCommand("C:", "C:", "");
139 	// ... but it does split on slash
140 	assert_DoCommand("C:\\", "C:", "\\");
141 }
142 
TEST_F(DOS_Shell_CMDSTest,DoCommand_Nospace_Dot_Handling)143 TEST_F(DOS_Shell_CMDSTest, DoCommand_Nospace_Dot_Handling)
144 {
145 	assert_DoCommand("DIR.EXE", "DIR", ".EXE");
146 	assert_DoCommand("CD..", "CD", "..");
147 	assert_DoCommand("CD....", "CD", "....");
148 }
149 
TEST_F(DOS_Shell_CMDSTest,DoCommand_Nospace_Slash_Handling)150 TEST_F(DOS_Shell_CMDSTest, DoCommand_Nospace_Slash_Handling)
151 {
152 	assert_DoCommand("CD\\DIRECTORY", "CD", "\\DIRECTORY");
153 	assert_DoCommand("CD\\", "CD", "\\");
154 }
155 
TEST_F(DOS_Shell_CMDSTest,CMD_ECHO_off_on)156 TEST_F(DOS_Shell_CMDSTest, CMD_ECHO_off_on)
157 {
158 	MockDOS_Shell shell;
159 	EXPECT_TRUE(shell.echo); // should be the default
160 	EXPECT_CALL(shell, WriteOut(_, _)).Times(0);
161 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>("OFF")); });
162 	EXPECT_FALSE(shell.echo);
163 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>("ON")); });
164 	EXPECT_TRUE(shell.echo);
165 }
166 
TEST_F(DOS_Shell_CMDSTest,CMD_ECHO_space_handling)167 TEST_F(DOS_Shell_CMDSTest, CMD_ECHO_space_handling)
168 {
169 	MockDOS_Shell shell;
170 
171 	EXPECT_TRUE(shell.echo);
172 	EXPECT_CALL(shell, WriteOut(_, StrEq("OFF "))).Times(1);
173 	// this DOES NOT trigger ECHO OFF (trailing space causes it to not)
174 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>(" OFF ")); });
175 	EXPECT_TRUE(shell.echo);
176 
177 	EXPECT_CALL(shell, WriteOut(_, StrEq("FF "))).Times(1);
178 	// this DOES NOT trigger ECHO OFF (initial 'O' gets stripped)
179 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>("OFF ")); });
180 	EXPECT_TRUE(shell.echo);
181 
182 	// no trailing space, echo off should work
183 	EXPECT_CALL(shell, WriteOut(_, _)).Times(0);
184 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>(" OFF")); });
185 	// check that OFF worked properly, despite spaces
186 	EXPECT_FALSE(shell.echo);
187 
188 	// NOTE: the expected string here is missing the leading char of the
189 	// input to ECHO. the first char is stripped as it's assumed it will be
190 	// a space, period or slash.
191 	EXPECT_CALL(shell, WriteOut(_, StrEq("    HI "))).Times(1);
192 	EXPECT_NO_THROW({ shell.CMD_ECHO(const_cast<char *>(".    HI ")); });
193 }
194 
195 } // namespace
196