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
18using LIBC_NAMESPACE::cpp::array;
19
20namespace test_globals {
21char *optarg;
22int optind = 1;
23int optopt;
24int opterr = 1;
25
26unsigned 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.
31void 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
37static 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
42ssize_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
49static 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.
55FILE *memopen(char **pos) {
56 return LIBC_NAMESPACE::fopencookie(cookie: pos, mode: "w", desc: cookie);
57}
58
59struct 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.
84char *operator"" _c(const char *c, size_t) { return const_cast<char *>(c); }
85
86TEST_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
112TEST_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
121TEST_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
132TEST_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
144TEST_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
152TEST_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
160TEST_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

source code of libc/test/src/unistd/getopt_test.cpp