aboutsummaryrefslogtreecommitdiff
path: root/libc/test/src/unistd/getopt_test.cpp
blob: 1ca7c99e1ce37312ca96db5cc4043170b201db5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//===-- Unittests for getopt ----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/unistd/getopt.h"
#include "test/UnitTest/Test.h"

#include "src/__support/CPP/array.h"
#include "src/stdio/fflush.h"
#include "src/stdio/fopencookie.h"

#include <stdio.h>

using LIBC_NAMESPACE::cpp::array;

namespace test_globals {
char *optarg;
int optind = 1;
int optopt;
int opterr = 1;

unsigned optpos;
} // namespace test_globals

// This can't be a constructor because it will get run before the constructor
// which sets the default state in getopt.
void set_state(FILE *errstream) {
  LIBC_NAMESPACE::impl::set_getopt_state(
      &test_globals::optarg, &test_globals::optind, &test_globals::optopt,
      &test_globals::optpos, &test_globals::opterr, errstream);
}

static void my_memcpy(char *dest, const char *src, size_t size) {
  for (size_t i = 0; i < size; i++)
    dest[i] = src[i];
}

ssize_t cookie_write(void *cookie, const char *buf, size_t size) {
  char **pos = static_cast<char **>(cookie);
  my_memcpy(*pos, buf, size);
  *pos += size;
  return size;
}

static cookie_io_functions_t cookie{nullptr, &cookie_write, nullptr, nullptr};

// TODO: <stdio> could be either llvm-libc's or the system libc's. The former
// doesn't currently support fmemopen but does have fopencookie. In the future
// just use that instead. This memopen does no error checking for the size
// of the buffer, etc.
FILE *memopen(char **pos) {
  return LIBC_NAMESPACE::fopencookie(pos, "w", cookie);
}

struct LlvmLibcGetoptTest : public LIBC_NAMESPACE::testing::Test {
  FILE *errstream;
  char buf[256];
  char *pos = buf;

  void reset_errstream() { pos = buf; }
  const char *get_error_msg() {
    LIBC_NAMESPACE::fflush(errstream);
    return buf;
  }

  void SetUp() override {
    ASSERT_TRUE(!!(errstream = memopen(&pos)));
    set_state(errstream);
    ASSERT_EQ(test_globals::optind, 1);
  }

  void TearDown() override {
    test_globals::optind = 1;
    test_globals::opterr = 1;
  }
};

// This is safe because getopt doesn't currently permute argv like GNU's getopt
// does so this just helps silence warnings.
char *operator"" _c(const char *c, size_t) { return const_cast<char *>(c); }

TEST_F(LlvmLibcGetoptTest, NoMatch) {
  array<char *, 3> argv{"prog"_c, "arg1"_c, nullptr};

  // optind >= argc
  EXPECT_EQ(LIBC_NAMESPACE::getopt(1, argv.data(), "..."), -1);

  // argv[optind] == nullptr
  test_globals::optind = 2;
  EXPECT_EQ(LIBC_NAMESPACE::getopt(100, argv.data(), "..."), -1);

  // argv[optind][0] != '-'
  test_globals::optind = 1;
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a"), -1);
  ASSERT_EQ(test_globals::optind, 1);

  // argv[optind] == "-"
  argv[1] = "-"_c;
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a"), -1);
  ASSERT_EQ(test_globals::optind, 1);

  // argv[optind] == "--", then return -1 and incremement optind
  argv[1] = "--"_c;
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a"), -1);
  EXPECT_EQ(test_globals::optind, 2);
}

TEST_F(LlvmLibcGetoptTest, WrongMatch) {
  array<char *, 3> argv{"prog"_c, "-b"_c, nullptr};

  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a"), int('?'));
  EXPECT_EQ(test_globals::optopt, (int)'b');
  EXPECT_EQ(test_globals::optind, 1);
  EXPECT_STREQ(get_error_msg(), "prog: illegal option -- b\n");
}

TEST_F(LlvmLibcGetoptTest, OpterrFalse) {
  array<char *, 3> argv{"prog"_c, "-b"_c, nullptr};

  test_globals::opterr = 0;
  set_state(errstream);
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "a"), int('?'));
  EXPECT_EQ(test_globals::optopt, (int)'b');
  EXPECT_EQ(test_globals::optind, 1);
  EXPECT_STREQ(get_error_msg(), "");
}

TEST_F(LlvmLibcGetoptTest, MissingArg) {
  array<char *, 3> argv{"prog"_c, "-b"_c, nullptr};

  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), ":b:"), (int)':');
  ASSERT_EQ(test_globals::optind, 1);
  EXPECT_STREQ(get_error_msg(), "prog: option requires an argument -- b\n");
  reset_errstream();
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "b:"), int('?'));
  EXPECT_EQ(test_globals::optind, 1);
  EXPECT_STREQ(get_error_msg(), "prog: option requires an argument -- b\n");
}

TEST_F(LlvmLibcGetoptTest, ParseArgInCurrent) {
  array<char *, 3> argv{"prog"_c, "-barg"_c, nullptr};

  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "b:"), (int)'b');
  EXPECT_STREQ(test_globals::optarg, "arg");
  EXPECT_EQ(test_globals::optind, 2);
}

TEST_F(LlvmLibcGetoptTest, ParseArgInNext) {
  array<char *, 4> argv{"prog"_c, "-b"_c, "arg"_c, nullptr};

  EXPECT_EQ(LIBC_NAMESPACE::getopt(3, argv.data(), "b:"), (int)'b');
  EXPECT_STREQ(test_globals::optarg, "arg");
  EXPECT_EQ(test_globals::optind, 3);
}

TEST_F(LlvmLibcGetoptTest, ParseMutliInOne) {
  array<char *, 3> argv{"prog"_c, "-abc"_c, nullptr};

  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc"), (int)'a');
  ASSERT_EQ(test_globals::optind, 1);
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc"), (int)'b');
  ASSERT_EQ(test_globals::optind, 1);
  EXPECT_EQ(LIBC_NAMESPACE::getopt(2, argv.data(), "abc"), (int)'c');
  EXPECT_EQ(test_globals::optind, 2);
}