aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Tooling/ToolingTestSupport.h
blob: bff0ebb0db2c3032b486c088877c653fda878abd (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
// SPDX-License-Identifier: GPL-3.0-or-later

// The private header has no stable include-root-relative guard name.
// NOLINTNEXTLINE(llvm-header-guard)
#ifndef RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H
#define RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H

#include "rcc/Support/Error.h"
#include "rcc/Tooling/ArtifactStore.h"
#include "rcc/Tooling/BackendInvocation.h"

#include <chrono>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <span>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>

#ifndef RCC_BACKEND_TOOLING_TEST_INPUT_DIR
#error "RCC_BACKEND_TOOLING_TEST_INPUT_DIR must be defined"
#endif

namespace tooling_test {

inline int fail(std::string_view Message) {
  std::cerr << Message << '\n';
  return 1;
}

[[nodiscard]] inline rcc::Expected<std::string> readText(const std::filesystem::path &Path) {
  std::ifstream Input(Path, std::ios::binary);
  if (!Input) {
    return rcc::makeUnexpected(rcc::ErrorCode::IoError,
                               "cannot open test input '" + Path.string() + "'");
  }
  std::string Text((std::istreambuf_iterator<char>(Input)), std::istreambuf_iterator<char>());
  if (Input.bad()) {
    return rcc::makeUnexpected(rcc::ErrorCode::IoError,
                               "cannot read test input '" + Path.string() + "'");
  }
  return Text;
}

[[nodiscard]] inline rcc::Expected<void> writeText(const std::filesystem::path &Path,
                                                   std::string_view Text) {
  std::ofstream Output(Path, std::ios::binary | std::ios::trunc);
  if (!Output)
    return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot create test file");
  Output.write(Text.data(), static_cast<std::streamsize>(Text.size()));
  Output.close();
  if (!Output)
    return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot write test file");
  return {};
}

[[nodiscard]] inline rcc::Expected<void> writeBytes(const std::filesystem::path &Path,
                                                    std::span<const std::uint8_t> Bytes) {
  if (Bytes.empty())
    return writeText(Path, {});
  const auto *Data = reinterpret_cast<const char *>(Bytes.data());
  return writeText(Path, std::string_view(Data, Bytes.size()));
}

[[nodiscard]] inline rcc::Expected<std::string> readInput(std::string_view Name) {
  return readText(std::filesystem::path(RCC_BACKEND_TOOLING_TEST_INPUT_DIR) / Name);
}

[[nodiscard]] inline rcc::Expected<rcc::tooling::CanonicalMIR>
loadCanonicalInput(std::string_view Name, const rcc::TargetDescription &Target,
                   const rcc::tooling::BackendInvocation &Invocation) {
  auto Source = readInput(Name);
  if (!Source)
    return std::unexpected(Source.error());
  return rcc::tooling::parseAndCanonicalizeMIR(std::string(Name), std::move(*Source), Target,
                                               Invocation);
}

class TemporaryDirectory final {
public:
  TemporaryDirectory(const TemporaryDirectory &) = delete;
  TemporaryDirectory &operator=(const TemporaryDirectory &) = delete;

  TemporaryDirectory(TemporaryDirectory &&Other) noexcept : Path(std::move(Other.Path)) {
    Other.Path.clear();
  }
  TemporaryDirectory &operator=(TemporaryDirectory &&) = delete;

  ~TemporaryDirectory() {
    if (Path.empty())
      return;
    std::error_code Error;
    std::filesystem::remove_all(Path, Error);
  }

  [[nodiscard]] static rcc::Expected<TemporaryDirectory> create(std::string_view Prefix) {
    std::error_code Error;
    const std::filesystem::path Root = std::filesystem::temp_directory_path(Error);
    if (Error)
      return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot locate test temporary directory");
    const auto Stamp = std::chrono::steady_clock::now().time_since_epoch().count();
    for (std::size_t Attempt = 0; Attempt < 1024; ++Attempt) {
      const std::filesystem::path Candidate =
          Root /
          (std::string(Prefix) + "-" + std::to_string(Stamp) + "-" + std::to_string(Attempt));
      Error.clear();
      if (std::filesystem::create_directory(Candidate, Error))
        return TemporaryDirectory(Candidate);
    }
    return rcc::makeUnexpected(rcc::ErrorCode::IoError,
                               "cannot create a unique test temporary directory");
  }

  [[nodiscard]] const std::filesystem::path &path() const noexcept {
    return Path;
  }

private:
  explicit TemporaryDirectory(std::filesystem::path Path) : Path(std::move(Path)) {}

  std::filesystem::path Path;
};

} // namespace tooling_test

#endif // RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H