Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d141b51 authored by Chris Manton's avatar Chris Manton
Browse files

Add unit test log capture capability

Bug: 244092860
Test: gd/cert/run
Tag: #refactor
BYPASS_LONG_LINES_REASON: Bluetooth likes 120 lines

Change-Id: I1a31a7dc0a3e93963fa4a1f8905c8e26ba91fe8a
parent 8a3f31bf
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ filegroup {
filegroup {
    name: "BluetoothCommonTestSources",
    srcs: [
        ":BluetoothCommonTestingLogCapture",
        ":BluetoothCommonTestingLogCaptureTest",
        "bidi_queue_unittest.cc",
        "blocking_queue_unittest.cc",
        "byte_array_test.cc",
+13 −0
Original line number Diff line number Diff line
filegroup {
    name: "BluetoothCommonTestingLogCapture",
    srcs: [
            "log_capture.cc",
  ],
}

filegroup {
    name: "BluetoothCommonTestingLogCaptureTest",
    srcs: [
            "log_capture_test.cc",
    ],
}
+162 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "common/testing/log_capture.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <cstddef>
#include <sstream>
#include <string>

#include "os/log.h"

namespace {
constexpr char kTempFilename[] = "/tmp/bt_gtest_log_capture-XXXXXX";
constexpr size_t kTempFilenameMaxSize = 64;
constexpr size_t kBufferSize = 4096;
constexpr int kStandardErrorFd = STDERR_FILENO;
}  // namespace

namespace bluetooth {
namespace testing {

LogCapture::LogCapture() {
  fd_ = create_backing_store();
  if (fd_ == -1) {
    LOG_ERROR("Unable to create backing storage : %s", strerror(errno));
    return;
  }
  if (!set_non_blocking(fd_)) {
    LOG_ERROR("Unable to set socket non-blocking : %s", strerror(errno));
    return;
  }
  original_stderr_fd_ = fcntl(kStandardErrorFd, F_DUPFD_CLOEXEC);
  if (original_stderr_fd_ == -1) {
    LOG_ERROR("Unable to save original fd : %s", strerror(errno));
    return;
  }
  if (dup3(fd_, kStandardErrorFd, O_CLOEXEC) == -1) {
    LOG_ERROR("Unable to duplicate stderr fd : %s", strerror(errno));
    return;
  }
}

LogCapture::~LogCapture() {
  Rewind()->Flush();
  clean_up();
}

LogCapture* LogCapture::Rewind() {
  if (fd_ != -1) {
    if (lseek(fd_, 0, SEEK_SET) != 0) {
      LOG_ERROR("Unable to rewind log capture : %s", strerror(errno));
    }
  }
  return this;
}

bool LogCapture::Find(std::string to_find) {
  std::string str = this->Read();
  return str.find(to_find) != std::string::npos;
}

void LogCapture::Flush() {
  if (fd_ != -1 && original_stderr_fd_ != -1) {
    ssize_t sz{-1};
    do {
      char buf[kBufferSize];
      sz = read(fd_, buf, sizeof(buf));
      if (sz > 0) {
        write(original_stderr_fd_, buf, sz);
      }
    } while (sz == kBufferSize);
  }
}

void LogCapture::Reset() {
  if (fd_ != -1) {
    if (ftruncate(fd_, 0UL) == -1) {
      LOG_ERROR("Unable to truncate backing storage : %s", strerror(errno));
    }
    this->Rewind();
  }
}

std::string LogCapture::Read() {
  if (fd_ == -1) {
    return std::string();
  }
  std::ostringstream oss;
  ssize_t sz{-1};
  do {
    char buf[kBufferSize];
    sz = read(fd_, buf, sizeof(buf));
    if (sz > 0) {
      oss << buf;
    }
  } while (sz == kBufferSize);
  return oss.str();
}

size_t LogCapture::Size() const {
  size_t size{0UL};
  struct stat statbuf;
  if (fd_ != -1 && fstat(fd_, &statbuf) != -1) {
    size = statbuf.st_size;
  }
  return size;
}

int LogCapture::create_backing_store() const {
  char backing_store_filename[kTempFilenameMaxSize];
  strncpy(backing_store_filename, kTempFilename, kTempFilenameMaxSize);
  int fd = mkstemp(backing_store_filename);
  if (fd != -1) {
    unlink(backing_store_filename);
  }
  return fd;
}

bool LogCapture::set_non_blocking(int fd) const {
  int flags = fcntl(fd, F_GETFL, 0);
  if (flags == -1) {
    LOG_ERROR("Unable to get file descriptor flags : %s", strerror(errno));
    return false;
  }
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
    LOG_ERROR("Unable to set file descriptor flags : %s", strerror(errno));
    return false;
  }
  return true;
}

void LogCapture::clean_up() {
  if (original_stderr_fd_ != -1) {
    if (dup3(original_stderr_fd_, kStandardErrorFd, O_CLOEXEC) != kStandardErrorFd) {
      LOG_ERROR("Unable to restore original fd : %s", strerror(errno));
    }
  }
  if (fd_ != -1) {
    close(fd_);
    fd_ = -1;
  }
}

}  // namespace testing
}  // namespace bluetooth
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstddef>
#include <string>

namespace bluetooth {
namespace testing {

class LogCapture {
 public:
  LogCapture();
  ~LogCapture();

  // Rewind file pointer to start of log
  // Returns a |this| pointer for chaining.  See |Find|
  LogCapture* Rewind();
  // Searches from filepointer to end of file for |to_find| string
  // Returns true if found, false otherwise
  bool Find(std::string to_find);
  // Reads and returns the entirety of the backing store into a string
  std::string Read();
  // Flushes contents of log capture back to |stderr|
  void Flush();
  // Returns the backing store size in bytes
  size_t Size() const;
  // Truncates and resets the file pointer discarding all logs up to this point
  void Reset();

 private:
  int create_backing_store() const;
  bool set_non_blocking(int fd) const;
  void clean_up();

  int fd_{-1};
  int original_stderr_fd_{-1};
};

}  // namespace testing
}  // namespace bluetooth
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "log_capture.h"

#include <gtest/gtest.h>

#include <cstring>
#include <memory>
#include <string>

#include "common/init_flags.h"
#include "os/log.h"

namespace {
const char* test_flags[] = {
    "INIT_logging_debug_enabled_for_all=true",
    nullptr,
};

constexpr char kEmptyLine[] = "";
constexpr char kLogError[] = "LOG_ERROR";
constexpr char kLogWarn[] = "LOG_WARN";
constexpr char kLogInfo[] = "LOG_INFO";
constexpr char kLogDebug[] = "LOG_DEBUG";
constexpr char kLogVerbose[] = "LOG_VERBOSE";

}  // namespace

namespace bluetooth {
namespace testing {

class LogCaptureTest : public ::testing::Test {
 protected:
  void SetUp() override {}

  void TearDown() override {}

  // The line number is part of the log output and must be factored out
  size_t CalibrateOneLine(const char* log_line) {
    LOG_INFO("%s", log_line);
    return strlen(log_line);
  }
};

TEST_F(LogCaptureTest, no_output) {
  std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();

  ASSERT_TRUE(log_capture->Size() == 0);
}

TEST_F(LogCaptureTest, truncate) {
  std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();

  CalibrateOneLine(kLogError);
  size_t size = log_capture->Size();
  ASSERT_TRUE(size > 0);

  log_capture->Reset();
  ASSERT_EQ(0UL, log_capture->Size());

  CalibrateOneLine(kLogError);
  ASSERT_EQ(size, log_capture->Size());
}

TEST_F(LogCaptureTest, log_size) {
  std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();

  CalibrateOneLine(kEmptyLine);
  size_t empty_line_size = log_capture->Size();
  log_capture->Reset();

  std::vector<std::string> log_lines = {
      kLogError,
      kLogWarn,
      kLogInfo,
  };

  size_t msg_size{0};
  for (auto& log_line : log_lines) {
    msg_size += CalibrateOneLine(log_line.c_str());
  }

  ASSERT_EQ(empty_line_size * log_lines.size() + msg_size, log_capture->Size());

  ASSERT_TRUE(log_capture->Rewind()->Find(kLogError));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogWarn));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogInfo));
}

TEST_F(LogCaptureTest, typical) {
  std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();

  LOG_ERROR("%s", kLogError);
  LOG_WARN("%s", kLogWarn);
  LOG_INFO("%s", kLogInfo);
  LOG_DEBUG("%s", kLogDebug);
  LOG_VERBOSE("%s", kLogVerbose);

  ASSERT_TRUE(log_capture->Rewind()->Find(kLogError));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogWarn));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogInfo));
  ASSERT_FALSE(log_capture->Rewind()->Find(kLogDebug));
  ASSERT_FALSE(log_capture->Rewind()->Find(kLogVerbose));
}

TEST_F(LogCaptureTest, with_logging_debug_enabled_for_all) {
  bluetooth::common::InitFlags::Load(test_flags);
  std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();

  LOG_ERROR("%s", kLogError);
  LOG_WARN("%s", kLogWarn);
  LOG_INFO("%s", kLogInfo);
  LOG_DEBUG("%s", kLogDebug);
  LOG_VERBOSE("%s", kLogVerbose);

  ASSERT_TRUE(log_capture->Rewind()->Find(kLogError));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogWarn));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogInfo));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogDebug));
  ASSERT_TRUE(log_capture->Rewind()->Find(kLogVerbose));
  bluetooth::common::InitFlags::Load(nullptr);
}

}  // namespace testing
}  // namespace bluetooth