debug-test.c++ 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Kenton Varda's avatar
Kenton Varda committed
24
#include "debug.h"
25 26 27 28
#include "exception.h"
#include <gtest/gtest.h>
#include <string>
#include <stdio.h>
29
#include <unistd.h>
30
#include <signal.h>
31 32
#include <errno.h>
#include <string.h>
33
#include <exception>
34 35
#include <sys/types.h>
#include <sys/wait.h>
36

37
namespace kj {
38
namespace _ {  // private
39 40 41 42 43 44 45 46 47 48
namespace {

class MockException {};

class MockExceptionCallback: public ExceptionCallback {
public:
  ~MockExceptionCallback() {}

  std::string text;

49 50 51 52 53 54 55
  int outputPipe = -1;

  bool forkForDeathTest() {
    // This is called when exceptions are disabled.  We fork the process instead and then expect
    // the child to die.

    int pipeFds[2];
Kenton Varda's avatar
Kenton Varda committed
56
    KJ_SYSCALL(pipe(pipeFds));
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
    pid_t child = fork();
    if (child == 0) {
      // This is the child!
      close(pipeFds[0]);
      outputPipe = pipeFds[1];
      return true;
    } else {
      close(pipeFds[1]);

      // Read child error messages into our local buffer.
      char buf[1024];
      for (;;) {
        ssize_t n = read(pipeFds[0], buf, sizeof(buf));
        if (n < 0) {
          if (errno == EINTR) {
            continue;
          } else {
            break;
          }
        } else if (n == 0) {
          break;
        } else {
          text.append(buf, n);
        }
      }

      close(pipeFds[0]);

      // Get exit status.
      int status;
      do {
        if (waitpid(child, &status, 0) < 0) {
          if (errno == EINTR) {
            continue;
          } else {
            ADD_FAILURE() << "waidpid: " << strerror(errno);
            return false;
          }
        }
      } while (false);

      EXPECT_TRUE(WIFEXITED(status));
      EXPECT_EQ(74, WEXITSTATUS(status));

      return false;
    }
  }

  void flush() {
    if (outputPipe != -1) {
      const char* pos = &*text.begin();
      const char* end = pos + text.size();

      while (pos < end) {
        ssize_t n = write(outputPipe, pos, end - pos);
        if (n < 0) {
          if (errno == EINTR) {
            continue;
          } else {
            break;  // Give up on error.
          }
        }
        pos += n;
      }

      text.clear();
    }
  }

126 127
  void onRecoverableException(Exception&& exception) override {
    text += "recoverable exception: ";
128
    auto what = str(exception);
129 130
    // Discard the stack trace.
    const char* end = strstr(what.cStr(), "\nstack: ");
131
    if (end == nullptr) {
132
      text += what.cStr();
133
    } else {
134
      text.append(what.cStr(), end);
135
    }
136
    text += '\n';
137
    flush();
138 139 140 141
  }

  void onFatalException(Exception&& exception) override {
    text += "fatal exception: ";
142
    auto what = str(exception);
143 144
    // Discard the stack trace.
    const char* end = strstr(what.cStr(), "\nstack: ");
145
    if (end == nullptr) {
146
      text += what.cStr();
147
    } else {
148
      text.append(what.cStr(), end);
149
    }
150
    text += '\n';
151 152 153 154 155 156 157 158 159
    flush();
#if KJ_NO_EXCEPTIONS
    if (outputPipe >= 0) {
      // This is a child process.  We got what we want, now exit quickly without writing any
      // additional messages, with a status code that the parent will interpret as "exited in the
      // way we expected".
      _exit(74);
    }
#else
160
    throw MockException();
161
#endif
162 163
  }

164
  void logMessage(const char* file, int line, int contextDepth, String&& text) override {
165
    this->text += "log message: ";
166
    text = str(file, ":", line, ":+", contextDepth, ": ", mv(text));
167 168 169 170
    this->text.append(text.begin(), text.end());
  }
};

171 172 173 174 175 176
#if KJ_NO_EXCEPTIONS
#define EXPECT_FATAL(code) if (mockCallback.forkForDeathTest()) { code; abort(); }
#else
#define EXPECT_FATAL(code) EXPECT_THROW(code, MockException);
#endif

177 178 179 180 181 182 183 184
std::string fileLine(std::string file, int line) {
  file += ':';
  char buffer[32];
  sprintf(buffer, "%d", line);
  file += buffer;
  return file;
}

185
TEST(Debug, Log) {
186 187 188
  MockExceptionCallback mockCallback;
  int line;

189
  KJ_LOG(WARNING, "Hello world!"); line = __LINE__;
190
  EXPECT_EQ("log message: " + fileLine(__FILE__, line) + ":+0: warning: Hello world!\n",
191 192 193 194 195 196
            mockCallback.text);
  mockCallback.text.clear();

  int i = 123;
  const char* str = "foo";

197
  KJ_LOG(ERROR, i, str); line = __LINE__;
198
  EXPECT_EQ("log message: " + fileLine(__FILE__, line) + ":+0: error: i = 123; str = foo\n",
199 200 201
            mockCallback.text);
  mockCallback.text.clear();

202
  KJ_DBG("Some debug text."); line = __LINE__;
203
  EXPECT_EQ("log message: " + fileLine(__FILE__, line) + ":+0: debug: Some debug text.\n",
204 205 206 207 208 209 210 211 212
            mockCallback.text);
  mockCallback.text.clear();

  // INFO logging is disabled by default.
  KJ_LOG(INFO, "Info."); line = __LINE__;
  EXPECT_EQ("", mockCallback.text);
  mockCallback.text.clear();

  // Enable it.
Kenton Varda's avatar
Kenton Varda committed
213
  Debug::setLogLevel(Debug::Severity::INFO);
214
  KJ_LOG(INFO, "Some text."); line = __LINE__;
215
  EXPECT_EQ("log message: " + fileLine(__FILE__, line) + ":+0: info: Some text.\n",
216 217 218 219
            mockCallback.text);
  mockCallback.text.clear();

  // Back to default.
Kenton Varda's avatar
Kenton Varda committed
220
  Debug::setLogLevel(Debug::Severity::WARNING);
221

222
  KJ_ASSERT(1 == 1);
223
  EXPECT_FATAL(KJ_ASSERT(1 == 2)); line = __LINE__;
224 225 226 227
  EXPECT_EQ("fatal exception: " + fileLine(__FILE__, line) + ": bug in code: expected "
            "1 == 2\n", mockCallback.text);
  mockCallback.text.clear();

228
  KJ_ASSERT(1 == 1) {
229
    ADD_FAILURE() << "Shouldn't call recovery code when check passes.";
230
    break;
231 232 233
  };

  bool recovered = false;
234
  KJ_ASSERT(1 == 2, "1 is not 2") { recovered = true; break; } line = __LINE__;
235 236 237 238 239
  EXPECT_EQ("recoverable exception: " + fileLine(__FILE__, line) + ": bug in code: expected "
            "1 == 2; 1 is not 2\n", mockCallback.text);
  EXPECT_TRUE(recovered);
  mockCallback.text.clear();

240
  EXPECT_FATAL(KJ_ASSERT(1 == 2, i, "hi", str)); line = __LINE__;
241 242 243 244
  EXPECT_EQ("fatal exception: " + fileLine(__FILE__, line) + ": bug in code: expected "
            "1 == 2; i = 123; hi; str = foo\n", mockCallback.text);
  mockCallback.text.clear();

245
  EXPECT_FATAL(KJ_REQUIRE(1 == 2, i, "hi", str)); line = __LINE__;
246
  EXPECT_EQ("fatal exception: " + fileLine(__FILE__, line) + ": requirement not met: expected "
247 248
            "1 == 2; i = 123; hi; str = foo\n", mockCallback.text);
  mockCallback.text.clear();
249

250
  EXPECT_FATAL(KJ_FAIL_ASSERT("foo")); line = __LINE__;
251 252 253 254 255
  EXPECT_EQ("fatal exception: " + fileLine(__FILE__, line) + ": bug in code: foo\n",
            mockCallback.text);
  mockCallback.text.clear();
}

256
TEST(Debug, Catch) {
257 258
  int line;

259 260 261 262 263 264 265 266
  {
    // Catch recoverable as kj::Exception.
    Maybe<Exception> exception = kj::runCatchingExceptions([&](){
      line = __LINE__; KJ_FAIL_ASSERT("foo") { break; }
    });

    KJ_IF_MAYBE(e, exception) {
      String what = str(*e);
267 268 269 270
      KJ_IF_MAYBE(eol, what.findFirst('\n')) {
        what = kj::str(what.slice(0, *eol));
      }
      std::string text(what.cStr());
271 272 273 274
      EXPECT_EQ(fileLine(__FILE__, line) + ": bug in code: foo", text);
    } else {
      ADD_FAILURE() << "Expected exception.";
    }
275 276
  }

277
#if !KJ_NO_EXCEPTIONS
278 279 280 281 282 283 284 285
  {
    // Catch fatal as kj::Exception.
    Maybe<Exception> exception = kj::runCatchingExceptions([&](){
      line = __LINE__; KJ_FAIL_ASSERT("foo");
    });

    KJ_IF_MAYBE(e, exception) {
      String what = str(*e);
286 287 288 289
      KJ_IF_MAYBE(eol, what.findFirst('\n')) {
        what = kj::str(what.slice(0, *eol));
      }
      std::string text(what.cStr());
290 291 292 293 294 295 296 297 298 299 300 301
      EXPECT_EQ(fileLine(__FILE__, line) + ": bug in code: foo", text);
    } else {
      ADD_FAILURE() << "Expected exception.";
    }
  }

  {
    // Catch as std::exception.
    try {
      line = __LINE__; KJ_FAIL_ASSERT("foo");
      ADD_FAILURE() << "Expected exception.";
    } catch (const std::exception& e) {
302
      kj::StringPtr what = e.what();
Kenton Varda's avatar
Kenton Varda committed
303
      std::string text;
304 305 306 307 308
      KJ_IF_MAYBE(eol, what.findFirst('\n')) {
        text.assign(what.cStr(), *eol);
      } else {
        text.assign(what.cStr());
      }
309 310
      EXPECT_EQ(fileLine(__FILE__, line) + ": bug in code: foo", text);
    }
311
  }
312
#endif
313 314
}

315
TEST(Debug, Syscall) {
316 317 318 319 320 321
  MockExceptionCallback mockCallback;
  int line;

  int i = 123;
  const char* str = "foo";

322 323
  int fd;
  KJ_SYSCALL(fd = dup(STDIN_FILENO));
324
  KJ_SYSCALL(close(fd));
325
  EXPECT_FATAL(KJ_SYSCALL(close(fd), i, "bar", str)); line = __LINE__;
326 327 328 329 330 331
  EXPECT_EQ("fatal exception: " + fileLine(__FILE__, line) + ": error from OS: close(fd): "
            + strerror(EBADF) + "; i = 123; bar; str = foo\n", mockCallback.text);
  mockCallback.text.clear();

  int result = 0;
  bool recovered = false;
332
  KJ_SYSCALL(result = close(fd), i, "bar", str) { recovered = true; break; } line = __LINE__;
333 334 335 336
  EXPECT_EQ("recoverable exception: " + fileLine(__FILE__, line) + ": error from OS: close(fd): "
            + strerror(EBADF) + "; i = 123; bar; str = foo\n", mockCallback.text);
  EXPECT_LT(result, 0);
  EXPECT_TRUE(recovered);
337 338
}

339
TEST(Debug, Context) {
340 341 342
  MockExceptionCallback mockCallback;

  {
343
    KJ_CONTEXT("foo"); int cline = __LINE__;
344

345 346 347 348 349 350
    KJ_LOG(WARNING, "blah"); int line = __LINE__;
    EXPECT_EQ("log message: " + fileLine(__FILE__, cline) + ":+0: context: foo\n"
              "log message: " + fileLine(__FILE__, line) + ":+1: warning: blah\n",
              mockCallback.text);
    mockCallback.text.clear();

351
    EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line = __LINE__;
352 353 354 355 356 357 358 359
    EXPECT_EQ("fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
              + fileLine(__FILE__, line) + ": bug in code: bar\n",
              mockCallback.text);
    mockCallback.text.clear();

    {
      int i = 123;
      const char* str = "qux";
360
      KJ_CONTEXT("baz", i, "corge", str); int cline2 = __LINE__;
361
      EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line = __LINE__;
362 363 364 365 366 367 368 369 370

      EXPECT_EQ("fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
                + fileLine(__FILE__, cline2) + ": context: baz; i = 123; corge; str = qux\n"
                + fileLine(__FILE__, line) + ": bug in code: bar\n",
                mockCallback.text);
      mockCallback.text.clear();
    }

    {
371
      KJ_CONTEXT("grault"); int cline2 = __LINE__;
372
      EXPECT_FATAL(KJ_FAIL_ASSERT("bar")); line = __LINE__;
373 374 375 376 377 378 379 380 381 382

      EXPECT_EQ("fatal exception: " + fileLine(__FILE__, cline) + ": context: foo\n"
                + fileLine(__FILE__, cline2) + ": context: grault\n"
                + fileLine(__FILE__, line) + ": bug in code: bar\n",
                mockCallback.text);
      mockCallback.text.clear();
    }
  }
}

383
}  // namespace
384
}  // namespace _ (private)
385
}  // namespace kj