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