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