1 /*
2  * Copyright (c) 2021 Nordic Semiconductor
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /** @file
8  *  @brief Interactive getopt test suite
9  *
10  */
11 
12 #include <getopt.h>
13 #include <string.h>
14 
15 #include <zephyr/kernel.h>
16 #include <zephyr/posix/unistd.h>
17 #include <zephyr/ztest.h>
18 
ZTEST(posix_c_lib_ext,test_getopt_basic)19 ZTEST(posix_c_lib_ext, test_getopt_basic)
20 {
21 	static const char *const nargv[] = {
22 		"cmd_name", "-b", "-a", "-h", "-c", "-l", "-h", "-a", "-i", "-w",
23 	};
24 	static const char *accepted_opt = "abchw";
25 	static const char *expected = "bahc?ha?w";
26 	size_t argc = ARRAY_SIZE(nargv);
27 	int cnt = 0;
28 	int c;
29 	char **argv;
30 
31 	argv = (char **)nargv;
32 
33 	/* Get state of the current thread */
34 	getopt_init();
35 
36 	do {
37 		c = getopt(argc, argv, accepted_opt);
38 		if (cnt >= strlen(expected)) {
39 			break;
40 		}
41 
42 		zassert_equal(c, expected[cnt++], "unexpected opt character");
43 	} while (c != -1);
44 
45 	c = getopt(argc, argv, accepted_opt);
46 	zassert_equal(c, -1, "unexpected opt character");
47 }
48 
49 enum getopt_idx {
50 	GETOPT_IDX_CMD_NAME,
51 	GETOPT_IDX_OPTION1,
52 	GETOPT_IDX_OPTION2,
53 	GETOPT_IDX_OPTARG
54 };
55 
ZTEST(posix_c_lib_ext,test_getopt)56 ZTEST(posix_c_lib_ext, test_getopt)
57 {
58 	struct getopt_state *state;
59 	static const char *test_opts = "ac:";
60 	static const char *const nargv[] = {
61 		[GETOPT_IDX_CMD_NAME] = "cmd_name",
62 		[GETOPT_IDX_OPTION1] = "-a",
63 		[GETOPT_IDX_OPTION2] = "-c",
64 		[GETOPT_IDX_OPTARG] = "foo",
65 	};
66 	int argc = ARRAY_SIZE(nargv);
67 	char **argv;
68 	int c;
69 
70 	/* Get state of the current thread */
71 	getopt_init();
72 
73 	argv = (char **)nargv;
74 
75 	/* Test uknown option */
76 
77 	c = getopt(argc, argv, test_opts);
78 	zassert_equal(c, 'a', "unexpected opt character");
79 	c = getopt(argc, argv, test_opts);
80 	zassert_equal(c, 'c', "unexpected opt character");
81 
82 	c = getopt(argc, argv, test_opts);
83 	state = getopt_state_get();
84 
85 	/* Thread safe usge: */
86 	zassert_equal(0, strcmp(argv[GETOPT_IDX_OPTARG], state->optarg),
87 		      "unexpected optarg result");
88 	/* Non thread safe usage: */
89 	zassert_equal(0, strcmp(argv[GETOPT_IDX_OPTARG], optarg), "unexpected optarg result");
90 }
91 
92 enum getopt_long_idx {
93 	GETOPT_LONG_IDX_CMD_NAME,
94 	GETOPT_LONG_IDX_VERBOSE,
95 	GETOPT_LONG_IDX_OPT,
96 	GETOPT_LONG_IDX_OPTARG
97 };
98 
ZTEST(posix_c_lib_ext,test_getopt_long)99 ZTEST(posix_c_lib_ext, test_getopt_long)
100 {
101 	/* Below test is based on example
102 	 * https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html
103 	 */
104 	struct getopt_state *state;
105 	int verbose_flag = 0;
106 	/* getopt_long stores the option index here. */
107 	int option_index = 0;
108 	char **argv;
109 	int c;
110 	struct option long_options[] = {
111 		/* These options set a flag. */
112 		{"verbose", no_argument, &verbose_flag, 1},
113 		{"brief", no_argument, &verbose_flag, 0},
114 		/* These options don’t set a flag.
115 		 * We distinguish them by their indices.
116 		 */
117 		{"add", no_argument, 0, 'a'},
118 		{"create", required_argument, 0, 'c'},
119 		{"delete", required_argument, 0, 'd'},
120 		{"long", required_argument, 0, 'e'},
121 		{0, 0, 0, 0},
122 	};
123 	static const char *accepted_opt = "ac:d:e:";
124 
125 	static const char *const argv1[] = {
126 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
127 		[GETOPT_LONG_IDX_VERBOSE] = "--verbose",
128 		[GETOPT_LONG_IDX_OPT] = "--create",
129 		[GETOPT_LONG_IDX_OPTARG] = "some_file",
130 	};
131 	int argc1 = ARRAY_SIZE(argv1);
132 
133 	static const char *const argv2[] = {
134 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
135 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
136 		[GETOPT_LONG_IDX_OPT] = "-d",
137 		[GETOPT_LONG_IDX_OPTARG] = "other_file",
138 	};
139 	int argc2 = ARRAY_SIZE(argv2);
140 
141 	static const char *const argv3[] = {
142 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
143 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
144 		[GETOPT_LONG_IDX_OPT] = "-a",
145 	};
146 	int argc3 = ARRAY_SIZE(argv3);
147 
148 	/* this test distinguish getopt_long and getopt_long_only functions */
149 	static const char *const argv4[] = {
150 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
151 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
152 		/* below should not be interpreted as "--long/-e" option */
153 		[GETOPT_LONG_IDX_OPT] = "-l",
154 		[GETOPT_LONG_IDX_OPTARG] = "long_argument",
155 	};
156 	int argc4 = ARRAY_SIZE(argv4);
157 
158 	/* Test scenario 1 */
159 	/* Get state of the current thread */
160 	getopt_init();
161 	argv = (char **)argv1;
162 	c = getopt_long(argc1, argv, accepted_opt, long_options, &option_index);
163 	zassert_equal(verbose_flag, 1, "verbose flag expected");
164 	c = getopt_long(argc1, argv, accepted_opt, long_options, &option_index);
165 	state = getopt_state_get();
166 	zassert_equal('c', c, "unexpected option");
167 	zassert_equal(0, strcmp(state->optarg, argv[GETOPT_LONG_IDX_OPTARG]), "unexpected optarg");
168 	c = getopt_long(argc1, argv, accepted_opt, long_options, &option_index);
169 	zassert_equal(-1, c, "getopt_long shall return -1");
170 
171 	/* Test scenario 2 */
172 	argv = (char **)argv2;
173 	getopt_init();
174 	c = getopt_long(argc2, argv, accepted_opt, long_options, &option_index);
175 	zassert_equal(verbose_flag, 0, "verbose flag expected");
176 	c = getopt_long(argc2, argv, accepted_opt, long_options, &option_index);
177 	zassert_equal('d', c, "unexpected option");
178 	state = getopt_state_get();
179 	zassert_equal(0, strcmp(state->optarg, argv[GETOPT_LONG_IDX_OPTARG]), "unexpected optarg");
180 	c = getopt_long(argc2, argv, accepted_opt, long_options, &option_index);
181 	zassert_equal(-1, c, "getopt_long shall return -1");
182 
183 	/* Test scenario 3 */
184 	argv = (char **)argv3;
185 	getopt_init();
186 	c = getopt_long(argc3, argv, accepted_opt, long_options, &option_index);
187 	zassert_equal(verbose_flag, 0, "verbose flag expected");
188 	c = getopt_long(argc3, argv, accepted_opt, long_options, &option_index);
189 	zassert_equal('a', c, "unexpected option");
190 	c = getopt_long(argc3, argv, accepted_opt, long_options, &option_index);
191 	zassert_equal(-1, c, "getopt_long shall return -1");
192 
193 	/* Test scenario 4 */
194 	argv = (char **)argv4;
195 	getopt_init();
196 	c = getopt_long(argc4, argv, accepted_opt, long_options, &option_index);
197 	zassert_equal(verbose_flag, 0, "verbose flag expected");
198 	c = getopt_long(argc4, argv, accepted_opt, long_options, &option_index);
199 	/* Function was called with option '-l'. It is expected it will be
200 	 * NOT evaluated to '--long' which has flag 'e'.
201 	 */
202 	zassert_not_equal('e', c, "unexpected option match");
203 	c = getopt_long(argc4, argv, accepted_opt, long_options, &option_index);
204 }
205 
ZTEST(posix_c_lib_ext,test_getopt_long_only)206 ZTEST(posix_c_lib_ext, test_getopt_long_only)
207 {
208 	/* Below test is based on example
209 	 * https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html
210 	 */
211 	struct getopt_state *state;
212 	int verbose_flag = 0;
213 	/* getopt_long stores the option index here. */
214 	int option_index = 0;
215 	char **argv;
216 	int c;
217 	struct option long_options[] = {
218 		/* These options set a flag. */
219 		{"verbose", no_argument, &verbose_flag, 1},
220 		{"brief", no_argument, &verbose_flag, 0},
221 		/* These options don’t set a flag.
222 		 * We distinguish them by their indices.
223 		 */
224 		{"add", no_argument, 0, 'a'},
225 		{"create", required_argument, 0, 'c'},
226 		{"delete", required_argument, 0, 'd'},
227 		{"long", required_argument, 0, 'e'},
228 		{0, 0, 0, 0},
229 	};
230 	static const char *accepted_opt = "ac:d:e:";
231 
232 	static const char *const argv1[] = {
233 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
234 		[GETOPT_LONG_IDX_VERBOSE] = "--verbose",
235 		[GETOPT_LONG_IDX_OPT] = "--create",
236 		[GETOPT_LONG_IDX_OPTARG] = "some_file",
237 	};
238 	int argc1 = ARRAY_SIZE(argv1);
239 
240 	static const char *const argv2[] = {
241 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
242 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
243 		[GETOPT_LONG_IDX_OPT] = "-d",
244 		[GETOPT_LONG_IDX_OPTARG] = "other_file",
245 	};
246 	int argc2 = ARRAY_SIZE(argv2);
247 
248 	static const char *const argv3[] = {
249 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
250 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
251 		[GETOPT_LONG_IDX_OPT] = "-a",
252 	};
253 	int argc3 = ARRAY_SIZE(argv3);
254 
255 	/* this test distinguish getopt_long and getopt_long_only functions */
256 	static const char *const argv4[] = {
257 		[GETOPT_LONG_IDX_CMD_NAME] = "cmd_name",
258 		[GETOPT_LONG_IDX_VERBOSE] = "--brief",
259 		/* below should be interpreted as "--long/-e" option */
260 		[GETOPT_LONG_IDX_OPT] = "-l",
261 		[GETOPT_LONG_IDX_OPTARG] = "long_argument",
262 	};
263 	int argc4 = ARRAY_SIZE(argv4);
264 
265 	/* Test scenario 1 */
266 	argv = (char **)argv1;
267 	getopt_init();
268 	c = getopt_long_only(argc1, argv, accepted_opt, long_options, &option_index);
269 	zassert_equal(verbose_flag, 1, "verbose flag expected");
270 	c = getopt_long_only(argc1, argv, accepted_opt, long_options, &option_index);
271 	state = getopt_state_get();
272 	zassert_equal('c', c, "unexpected option");
273 	zassert_equal(0, strcmp(state->optarg, argv[GETOPT_LONG_IDX_OPTARG]), "unexpected optarg");
274 	c = getopt_long_only(argc1, argv, accepted_opt, long_options, &option_index);
275 	zassert_equal(-1, c, "getopt_long_only shall return -1");
276 
277 	/* Test scenario 2 */
278 	argv = (char **)argv2;
279 	getopt_init();
280 	state = getopt_state_get();
281 	c = getopt_long_only(argc2, argv, accepted_opt, long_options, &option_index);
282 	zassert_equal(verbose_flag, 0, "verbose flag expected");
283 	c = getopt_long_only(argc2, argv, accepted_opt, long_options, &option_index);
284 	state = getopt_state_get();
285 	zassert_equal('d', c, "unexpected option");
286 	zassert_equal(0, strcmp(state->optarg, argv[GETOPT_LONG_IDX_OPTARG]), "unexpected optarg");
287 	c = getopt_long_only(argc2, argv, accepted_opt, long_options, &option_index);
288 	zassert_equal(-1, c, "getopt_long_only shall return -1");
289 
290 	/* Test scenario 3 */
291 	argv = (char **)argv3;
292 	getopt_init();
293 	c = getopt_long_only(argc3, argv, accepted_opt, long_options, &option_index);
294 	zassert_equal(verbose_flag, 0, "verbose flag expected");
295 	c = getopt_long_only(argc3, argv, accepted_opt, long_options, &option_index);
296 	zassert_equal('a', c, "unexpected option");
297 	c = getopt_long_only(argc3, argv, accepted_opt, long_options, &option_index);
298 	zassert_equal(-1, c, "getopt_long_only shall return -1");
299 
300 	/* Test scenario 4 */
301 	argv = (char **)argv4;
302 	getopt_init();
303 	c = getopt_long_only(argc4, argv, accepted_opt, long_options, &option_index);
304 	zassert_equal(verbose_flag, 0, "verbose flag expected");
305 	c = getopt_long_only(argc4, argv, accepted_opt, long_options, &option_index);
306 
307 	/* Function was called with option '-l'. It is expected it will be
308 	 * evaluated to '--long' which has flag 'e'.
309 	 */
310 	zassert_equal('e', c, "unexpected option");
311 	c = getopt_long_only(argc4, argv, accepted_opt, long_options, &option_index);
312 }
313