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