1 /* Copyright (c) 2015, Google Inc.
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15 #include "file_test.h"
16
17 #include <ctype.h>
18 #include <errno.h>
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <openssl/err.h>
24
25 #include "stl_compat.h"
26
27
FileTest(const char * path)28 FileTest::FileTest(const char *path) {
29 file_ = fopen(path, "r");
30 if (file_ == nullptr) {
31 fprintf(stderr, "Could not open file %s: %s.\n", path, strerror(errno));
32 }
33 }
34
~FileTest()35 FileTest::~FileTest() {
36 if (file_ != nullptr) {
37 fclose(file_);
38 }
39 }
40
41 // FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
42 // if there is none.
FindDelimiter(const char * str)43 static const char *FindDelimiter(const char *str) {
44 while (*str) {
45 if (*str == ':' || *str == '=') {
46 return str;
47 }
48 str++;
49 }
50 return nullptr;
51 }
52
53 // StripSpace returns a string containing up to |len| characters from |str| with
54 // leading and trailing whitespace removed.
StripSpace(const char * str,size_t len)55 static std::string StripSpace(const char *str, size_t len) {
56 // Remove leading space.
57 while (len > 0 && isspace(*str)) {
58 str++;
59 len--;
60 }
61 while (len > 0 && isspace(str[len-1])) {
62 len--;
63 }
64 return std::string(str, len);
65 }
66
ReadNext()67 FileTest::ReadResult FileTest::ReadNext() {
68 // If the previous test had unused attributes or block, it is an error.
69 if (!unused_attributes_.empty()) {
70 for (const std::string &key : unused_attributes_) {
71 PrintLine("Unused attribute: %s", key.c_str());
72 }
73 return kReadError;
74 }
75 if (!block_.empty() && !used_block_) {
76 PrintLine("Unused block");
77 return kReadError;
78 }
79
80 ClearTest();
81
82 bool in_block = false;
83 while (true) {
84 // Read the next line.
85 char buf[4096];
86 if (fgets(buf, sizeof(buf), file_) == nullptr) {
87 if (feof(file_)) {
88 if (in_block) {
89 fprintf(stderr, "Unterminated block.\n");
90 return kReadError;
91 }
92 // EOF is a valid terminator for a test.
93 return start_line_ > 0 ? kReadSuccess : kReadEOF;
94 }
95 fprintf(stderr, "Error reading from input.\n");
96 return kReadError;
97 }
98
99 line_++;
100 size_t len = strlen(buf);
101 // Check for truncation.
102 if (len > 0 && buf[len - 1] != '\n' && !feof(file_)) {
103 fprintf(stderr, "Line %u too long.\n", line_);
104 return kReadError;
105 }
106
107 bool is_delimiter = strncmp(buf, "---", 3) == 0;
108 if (in_block) {
109 block_ += buf;
110 if (is_delimiter) {
111 // Ending the block completes the test.
112 return kReadSuccess;
113 }
114 } else if (is_delimiter) {
115 if (start_line_ == 0) {
116 fprintf(stderr, "Line %u: Unexpected block.\n", line_);
117 return kReadError;
118 }
119 in_block = true;
120 block_ += buf;
121 } else if (buf[0] == '\n' || buf[0] == '\0') {
122 // Empty lines delimit tests.
123 if (start_line_ > 0) {
124 return kReadSuccess;
125 }
126 } else if (buf[0] != '#') { // Comment lines are ignored.
127 // Parse the line as an attribute.
128 const char *delimiter = FindDelimiter(buf);
129 if (delimiter == nullptr) {
130 fprintf(stderr, "Line %u: Could not parse attribute.\n", line_);
131 }
132 std::string key = StripSpace(buf, delimiter - buf);
133 std::string value = StripSpace(delimiter + 1,
134 buf + len - delimiter - 1);
135
136 unused_attributes_.insert(key);
137 attributes_[key] = value;
138 if (start_line_ == 0) {
139 // This is the start of a test.
140 type_ = key;
141 parameter_ = value;
142 start_line_ = line_;
143 }
144 }
145 }
146 }
147
PrintLine(const char * format,...)148 void FileTest::PrintLine(const char *format, ...) {
149 va_list args;
150 va_start(args, format);
151
152 fprintf(stderr, "Line %u: ", start_line_);
153 vfprintf(stderr, format, args);
154 fprintf(stderr, "\n");
155
156 va_end(args);
157 }
158
GetType()159 const std::string &FileTest::GetType() {
160 OnKeyUsed(type_);
161 return type_;
162 }
163
GetParameter()164 const std::string &FileTest::GetParameter() {
165 OnKeyUsed(type_);
166 return parameter_;
167 }
168
GetBlock()169 const std::string &FileTest::GetBlock() {
170 used_block_ = true;
171 return block_;
172 }
173
HasAttribute(const std::string & key)174 bool FileTest::HasAttribute(const std::string &key) {
175 OnKeyUsed(key);
176 return attributes_.count(key) > 0;
177 }
178
GetAttribute(std::string * out_value,const std::string & key)179 bool FileTest::GetAttribute(std::string *out_value, const std::string &key) {
180 OnKeyUsed(key);
181 auto iter = attributes_.find(key);
182 if (iter == attributes_.end()) {
183 PrintLine("Missing attribute '%s'.", key.c_str());
184 return false;
185 }
186 *out_value = iter->second;
187 return true;
188 }
189
GetAttributeOrDie(const std::string & key)190 const std::string &FileTest::GetAttributeOrDie(const std::string &key) {
191 if (!HasAttribute(key)) {
192 abort();
193 }
194 return attributes_[key];
195 }
196
FromHexDigit(uint8_t * out,char c)197 static bool FromHexDigit(uint8_t *out, char c) {
198 if ('0' <= c && c <= '9') {
199 *out = c - '0';
200 return true;
201 }
202 if ('a' <= c && c <= 'f') {
203 *out = c - 'a' + 10;
204 return true;
205 }
206 if ('A' <= c && c <= 'F') {
207 *out = c - 'A' + 10;
208 return true;
209 }
210 return false;
211 }
212
GetBytes(std::vector<uint8_t> * out,const std::string & key)213 bool FileTest::GetBytes(std::vector<uint8_t> *out, const std::string &key) {
214 std::string value;
215 if (!GetAttribute(&value, key)) {
216 return false;
217 }
218
219 if (value.size() >= 2 && value[0] == '"' && value[value.size() - 1] == '"') {
220 out->assign(value.begin() + 1, value.end() - 1);
221 return true;
222 }
223
224 if (value.size() % 2 != 0) {
225 PrintLine("Error decoding value: %s", value.c_str());
226 return false;
227 }
228 out->reserve(value.size() / 2);
229 for (size_t i = 0; i < value.size(); i += 2) {
230 uint8_t hi, lo;
231 if (!FromHexDigit(&hi, value[i]) || !FromHexDigit(&lo, value[i+1])) {
232 PrintLine("Error decoding value: %s", value.c_str());
233 return false;
234 }
235 out->push_back((hi << 4) | lo);
236 }
237 return true;
238 }
239
EncodeHex(const uint8_t * in,size_t in_len)240 static std::string EncodeHex(const uint8_t *in, size_t in_len) {
241 static const char kHexDigits[] = "0123456789abcdef";
242 std::string ret;
243 ret.reserve(in_len * 2);
244 for (size_t i = 0; i < in_len; i++) {
245 ret += kHexDigits[in[i] >> 4];
246 ret += kHexDigits[in[i] & 0xf];
247 }
248 return ret;
249 }
250
ExpectBytesEqual(const uint8_t * expected,size_t expected_len,const uint8_t * actual,size_t actual_len)251 bool FileTest::ExpectBytesEqual(const uint8_t *expected, size_t expected_len,
252 const uint8_t *actual, size_t actual_len) {
253 if (expected_len == actual_len &&
254 memcmp(expected, actual, expected_len) == 0) {
255 return true;
256 }
257
258 std::string expected_hex = EncodeHex(expected, expected_len);
259 std::string actual_hex = EncodeHex(actual, actual_len);
260 PrintLine("Expected: %s", expected_hex.c_str());
261 PrintLine("Actual: %s", actual_hex.c_str());
262 return false;
263 }
264
ClearTest()265 void FileTest::ClearTest() {
266 start_line_ = 0;
267 type_.clear();
268 parameter_.clear();
269 attributes_.clear();
270 block_.clear();
271 unused_attributes_.clear();
272 used_block_ = false;
273 }
274
OnKeyUsed(const std::string & key)275 void FileTest::OnKeyUsed(const std::string &key) {
276 unused_attributes_.erase(key);
277 }
278
FileTestMain(bool (* run_test)(FileTest * t,void * arg),void * arg,const char * path)279 int FileTestMain(bool (*run_test)(FileTest *t, void *arg), void *arg,
280 const char *path) {
281 FileTest t(path);
282 if (!t.is_open()) {
283 return 1;
284 }
285
286 bool failed = false;
287 while (true) {
288 FileTest::ReadResult ret = t.ReadNext();
289 if (ret == FileTest::kReadError) {
290 return 1;
291 } else if (ret == FileTest::kReadEOF) {
292 break;
293 }
294
295 bool result = run_test(&t, arg);
296 if (t.HasAttribute("Error")) {
297 if (result) {
298 t.PrintLine("Operation unexpectedly succeeded.");
299 failed = true;
300 continue;
301 }
302 uint32_t err = ERR_peek_error();
303 if (ERR_reason_error_string(err) != t.GetAttributeOrDie("Error")) {
304 t.PrintLine("Unexpected error; wanted '%s', got '%s'.",
305 t.GetAttributeOrDie("Error").c_str(),
306 ERR_reason_error_string(err));
307 failed = true;
308 continue;
309 }
310 ERR_clear_error();
311 } else if (!result) {
312 // In case the test itself doesn't print output, print something so the
313 // line number is reported.
314 t.PrintLine("Test failed");
315 ERR_print_errors_fp(stderr);
316 failed = true;
317 continue;
318 }
319 }
320
321 if (failed) {
322 return 1;
323 }
324
325 printf("PASS\n");
326 return 0;
327 }
328