1 | //===-- Unittests for getopt ----------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "src/unistd/getopt.h" |
10 | #include "test/UnitTest/Test.h" |
11 | |
12 | #include "src/__support/CPP/array.h" |
13 | #include "src/stdio/fflush.h" |
14 | #include "src/stdio/fopencookie.h" |
15 | |
16 | #include <stdio.h> |
17 | |
18 | using LIBC_NAMESPACE::cpp::array; |
19 | |
20 | namespace test_globals { |
21 | char *optarg; |
22 | int optind = 1; |
23 | int optopt; |
24 | int opterr = 1; |
25 | |
26 | unsigned optpos; |
27 | } // namespace test_globals |
28 | |
29 | // This can't be a constructor because it will get run before the constructor |
30 | // which sets the default state in getopt. |
31 | void set_state(FILE *errstream) { |
32 | LIBC_NAMESPACE::impl::set_getopt_state( |
33 | &test_globals::optarg, &test_globals::optind, &test_globals::optopt, |
34 | &test_globals::optpos, &test_globals::opterr, errstream); |
35 | } |
36 | |
37 | static void my_memcpy(char *dest, const char *src, size_t size) { |
38 | for (size_t i = 0; i < size; i++) |
39 | dest[i] = src[i]; |
40 | } |
41 | |
42 | ssize_t cookie_write(void *cookie, const char *buf, size_t size) { |
43 | char **pos = static_cast<char **>(cookie); |
44 | my_memcpy(dest: *pos, src: buf, size); |
45 | *pos += size; |
46 | return size; |
47 | } |
48 | |
49 | static cookie_io_functions_t cookie{.read: nullptr, .write: &cookie_write, .seek: nullptr, .close: nullptr}; |
50 | |
51 | // TODO: <stdio> could be either llvm-libc's or the system libc's. The former |
52 | // doesn't currently support fmemopen but does have fopencookie. In the future |
53 | // just use that instead. This memopen does no error checking for the size |
54 | // of the buffer, etc. |
55 | FILE *memopen(char **pos) { |
56 | return LIBC_NAMESPACE::fopencookie(cookie: pos, mode: "w" , desc: cookie); |
57 | } |
58 | |
59 | struct LlvmLibcGetoptTest : public LIBC_NAMESPACE::testing::Test { |
60 | FILE *errstream; |
61 | char buf[256]; |
62 | char *pos = buf; |
63 | |
64 | void reset_errstream() { pos = buf; } |
65 | const char *get_error_msg() { |
66 | LIBC_NAMESPACE::fflush(stream: errstream); |
67 | return buf; |
68 | } |
69 | |
70 | void SetUp() override { |
71 | ASSERT_TRUE(!!(errstream = memopen(&pos))); |
72 | set_state(errstream); |
73 | ASSERT_EQ(test_globals::optind, 1); |
74 | } |
75 | |
76 | void TearDown() override { |
77 | test_globals::optind = 1; |
78 | test_globals::opterr = 1; |
79 | } |
80 | }; |
81 | |
82 | // This is safe because getopt doesn't currently permute argv like GNU's getopt |
83 | // does so this just helps silence warnings. |
84 | char *operator"" _c(const char *c, size_t) { return const_cast<char *>(c); } |
85 | |
86 | TEST_F(LlvmLibcGetoptTest, NoMatch) { |
87 | array<char *, 3> argv{"prog"_c , "arg1"_c , nullptr}; |
88 | |
89 | // optind >= argc |
90 | EXPECT_EQ(LIBC_NAMESPACE::getopt(1, argv.data(), "..." ), -1); |
91 | |
92 | // argv[optind] == nullptr |
93 | test_globals::optind = 2; |
94 | EXPECT_EQ(LIBC_NAMESPACE::getopt(100, argv.data(), "..." ), -1); |
95 | |
96 | // argv[optind][0] != '-' |
97 | test_globals::optind = 1; |
98 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a" ), -1); |
99 | ASSERT_EQ(test_globals::optind, 1); |
100 | |
101 | // argv[optind] == "-" |
102 | argv[1] = "-"_c ; |
103 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a" ), -1); |
104 | ASSERT_EQ(test_globals::optind, 1); |
105 | |
106 | // argv[optind] == "--", then return -1 and incremement optind |
107 | argv[1] = "--"_c ; |
108 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a" ), -1); |
109 | EXPECT_EQ(test_globals::optind, 2); |
110 | } |
111 | |
112 | TEST_F(LlvmLibcGetoptTest, WrongMatch) { |
113 | array<char *, 3> argv{"prog"_c , "-b"_c , nullptr}; |
114 | |
115 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a" ), int('?')); |
116 | EXPECT_EQ(test_globals::optopt, (int)'b'); |
117 | EXPECT_EQ(test_globals::optind, 1); |
118 | EXPECT_STREQ(get_error_msg(), "prog: illegal option -- b\n" ); |
119 | } |
120 | |
121 | TEST_F(LlvmLibcGetoptTest, OpterrFalse) { |
122 | array<char *, 3> argv{"prog"_c , "-b"_c , nullptr}; |
123 | |
124 | test_globals::opterr = 0; |
125 | set_state(errstream); |
126 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a" ), int('?')); |
127 | EXPECT_EQ(test_globals::optopt, (int)'b'); |
128 | EXPECT_EQ(test_globals::optind, 1); |
129 | EXPECT_STREQ(get_error_msg(), "" ); |
130 | } |
131 | |
132 | TEST_F(LlvmLibcGetoptTest, MissingArg) { |
133 | array<char *, 3> argv{"prog"_c , "-b"_c , nullptr}; |
134 | |
135 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), ":b:" ), (int)':'); |
136 | ASSERT_EQ(test_globals::optind, 1); |
137 | EXPECT_STREQ(get_error_msg(), "prog: option requires an argument -- b\n" ); |
138 | reset_errstream(); |
139 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "b:" ), int('?')); |
140 | EXPECT_EQ(test_globals::optind, 1); |
141 | EXPECT_STREQ(get_error_msg(), "prog: option requires an argument -- b\n" ); |
142 | } |
143 | |
144 | TEST_F(LlvmLibcGetoptTest, ParseArgInCurrent) { |
145 | array<char *, 3> argv{"prog"_c , "-barg"_c , nullptr}; |
146 | |
147 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "b:" ), (int)'b'); |
148 | EXPECT_STREQ(test_globals::optarg, "arg" ); |
149 | EXPECT_EQ(test_globals::optind, 2); |
150 | } |
151 | |
152 | TEST_F(LlvmLibcGetoptTest, ParseArgInNext) { |
153 | array<char *, 4> argv{"prog"_c , "-b"_c , "arg"_c , nullptr}; |
154 | |
155 | EXPECT_EQ(LIBC_NAMESPACE::getopt(3, argv.data(), "b:" ), (int)'b'); |
156 | EXPECT_STREQ(test_globals::optarg, "arg" ); |
157 | EXPECT_EQ(test_globals::optind, 3); |
158 | } |
159 | |
160 | TEST_F(LlvmLibcGetoptTest, ParseMutliInOne) { |
161 | array<char *, 3> argv{"prog"_c , "-abc"_c , nullptr}; |
162 | |
163 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc" ), (int)'a'); |
164 | ASSERT_EQ(test_globals::optind, 1); |
165 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc" ), (int)'b'); |
166 | ASSERT_EQ(test_globals::optind, 1); |
167 | EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc" ), (int)'c'); |
168 | EXPECT_EQ(test_globals::optind, 2); |
169 | } |
170 | |