CCTLib
Calling-context and data-object attribution library for Intel Pin
cctlib_integration_test.cpp
Go to the documentation of this file.
1 // GoogleTest-driven integration tests for cctlib itself and the CCT-oriented
2 // test tools in tests/ (cct_client, cct_data_centric_client, cctlib_reader).
3 //
4 // These tests spawn Pin as a subprocess against the built tool .so files
5 // and the built victim executables, then parse the tool's output file.
6 // They complement the unit tests (which exercise data-structure invariants
7 // without Pin) with actual end-to-end runs.
8 //
9 // Environment:
10 // PIN_ROOT -- Pin install root (required)
11 // CCTLIB_ROOT -- repo root (auto-derived from build directory if unset)
12 
13 #include <cerrno>
14 #include <cstdio>
15 #include <cstdlib>
16 #include <cstring>
17 #include <fcntl.h>
18 #include <fstream>
19 #include <regex>
20 #include <sstream>
21 #include <string>
22 #include <sys/stat.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25 #include <vector>
26 
27 #include <gtest/gtest.h>
28 
29 namespace {
30 
31 // Returns the value of env var `name` or empty string.
32 std::string env(const char* name) {
33  const char* v = getenv(name);
34  return v ? v : "";
35 }
36 
37 // Resolve the CCTLib repo root. Set by the Makefile via CCTLIB_ROOT env var
38 // on the test invocation; falls back to walking up from CWD to find a
39 // characteristic file.
40 std::string cctlib_root() {
41  std::string r = env("CCTLIB_ROOT");
42  if (!r.empty()) return r;
43  // Fallback: assume we're in tests/gtest and repo root is two levels up.
44  return "../..";
45 }
46 
47 std::string pin_root() {
48  std::string r = env("PIN_ROOT");
49  return r;
50 }
51 
52 // Run Pin with (tool, argv...) and wait. Returns the child's exit code, or
53 // negative on spawn failure. stdout/stderr go to /dev/null unless
54 // CCTLIB_TEST_VERBOSE is set.
55 int run_pin(const std::string& tool, const std::vector<std::string>& args) {
56  std::string pin = pin_root() + "/pin";
57  std::vector<char*> argv;
58  argv.push_back(const_cast<char*>(pin.c_str()));
59  argv.push_back(const_cast<char*>("-t"));
60  argv.push_back(const_cast<char*>(tool.c_str()));
61  argv.push_back(const_cast<char*>("--"));
62  for (auto& a : args) argv.push_back(const_cast<char*>(a.c_str()));
63  argv.push_back(nullptr);
64 
65  pid_t pid = fork();
66  if (pid < 0) return -1;
67  if (pid == 0) {
68  if (!env("CCTLIB_TEST_VERBOSE").size()) {
69  int devnull = open("/dev/null", O_WRONLY);
70  if (devnull >= 0) {
71  dup2(devnull, 1);
72  dup2(devnull, 2);
73  close(devnull);
74  }
75  }
76  execv(pin.c_str(), argv.data());
77  _exit(127);
78  }
79  int status = 0;
80  if (waitpid(pid, &status, 0) < 0) return -1;
81  if (WIFEXITED(status)) return WEXITSTATUS(status);
82  return -2;
83 }
84 
85 // Find the newest file whose name starts with `prefix` in `dir`. Empty
86 // string if none. Deterministic when only one such file exists (typical
87 // after `rm -f prefix*` before the test).
88 std::string find_newest(const std::string& dir, const std::string& prefix) {
89  std::string cmd = "ls -t " + dir + "/" + prefix + "* 2>/dev/null | head -1";
90  FILE* p = popen(cmd.c_str(), "r");
91  if (!p) return {};
92  char buf[4096];
93  std::string out;
94  if (fgets(buf, sizeof(buf), p)) out = buf;
95  pclose(p);
96  if (!out.empty() && out.back() == '\n') out.pop_back();
97  return out;
98 }
99 
100 std::string read_file(const std::string& path) {
101  std::ifstream in(path);
102  std::stringstream ss;
103  ss << in.rdbuf();
104  return ss.str();
105 }
106 
107 // Cleans stale output files that the tools would otherwise pick up.
108 void cleanup(const std::string& dir, const std::string& prefix) {
109  std::string cmd = "rm -f " + dir + "/" + prefix + "*";
110  (void)system(cmd.c_str());
111 }
112 
113 class CCTLibIntegration : public ::testing::Test {
114  protected:
115  std::string root_;
116  void SetUp() override {
117  root_ = cctlib_root();
118  ASSERT_FALSE(pin_root().empty())
119  << "PIN_ROOT environment variable required for integration tests";
120  ASSERT_EQ(0, access((root_ + "/tests/obj-intel64/cct_client.so").c_str(), F_OK))
121  << "tests/obj-intel64/cct_client.so missing; run `make` first";
122  // Pin tools write their output file (client.out.*, deadspy.out.*)
123  // to CWD. chdir to the repo root so cleanup() and find_newest()
124  // agree with the tools on where to look.
125  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir(" << root_ << ") failed";
126  }
127 };
128 
129 // The vanilla cct_client should complete cleanly on a trivial target and
130 // produce a non-empty output file whose "Total call paths" line is > 0.
131 TEST_F(CCTLibIntegration, CctClientRunsCleanly) {
132  cleanup(root_, "client.out.");
133  int rc = run_pin(root_ + "/tests/obj-intel64/cct_client.so", {"/bin/echo", "hi"});
134  ASSERT_EQ(0, rc) << "cct_client on /bin/echo returned " << rc;
135  std::string outfile = find_newest(root_, "client.out.");
136  ASSERT_FALSE(outfile.empty()) << "no client.out.* produced";
137  std::string content = read_file(outfile);
138  std::regex re(R"(Total call paths=(\d+))");
139  std::smatch m;
140  ASSERT_TRUE(std::regex_search(content, m, re))
141  << "output missing Total call paths= line; content:\n" << content;
142  long paths = std::stol(m[1]);
143  EXPECT_GT(paths, 0);
144 }
145 
146 // cct_client_mem_only: same expectation.
147 TEST_F(CCTLibIntegration, CctClientMemOnlyRunsCleanly) {
148  cleanup(root_, "client.out.");
149  int rc = run_pin(root_ + "/tests/obj-intel64/cct_client_mem_only.so",
150  {"/bin/echo", "hi"});
151  ASSERT_EQ(0, rc);
152  std::string outfile = find_newest(root_, "client.out.");
153  ASSERT_FALSE(outfile.empty());
154  EXPECT_NE(std::string::npos, read_file(outfile).find("Total call paths="));
155 }
156 
157 // cct_data_centric_client: default USE_SHADOW_FOR_DATA_CENTRIC path. This is
158 // the client that exercised the shadow_memory `auto`->`auto&` fix from an
159 // earlier commit; the test's primary role now is regression coverage.
160 TEST_F(CCTLibIntegration, CctDataCentricClientRunsCleanly) {
161  cleanup(root_, "client.out.");
162  int rc = run_pin(root_ + "/tests/obj-intel64/cct_data_centric_client.so",
163  {"/bin/echo", "hi"});
164  ASSERT_EQ(0, rc);
165  std::string outfile = find_newest(root_, "client.out.");
166  ASSERT_FALSE(outfile.empty());
167 }
168 
169 // cct_data_centric_client_tree_based: USE_TREE_BASED_FOR_DATA_CENTRIC path.
170 TEST_F(CCTLibIntegration, CctDataCentricClientTreeBasedRunsCleanly) {
171  cleanup(root_, "client.out.");
172  int rc = run_pin(root_ + "/tests/obj-intel64/cct_data_centric_client_tree_based.so",
173  {"/bin/echo", "hi"});
174  ASSERT_EQ(0, rc);
175  std::string outfile = find_newest(root_, "client.out.");
176  ASSERT_FALSE(outfile.empty());
177 }
178 
179 // cctlib_reader is intentionally NOT tested here as an independent
180 // integration test. It expects to read a serialized CCT database
181 // (cctlib-database-*) that would normally be produced by a prior
182 // serialization run, and if the directory is missing the tool throws
183 // an uncaught std::string exception (a documented preexisting
184 // bug -- see the TODO in src/cctlib.cpp DeserializeMetadata). The
185 // existing top-level `make check` TEST7 exercises the tool loosely by
186 // running against `ls` and treating child-process exit-0 as pass; a
187 // proper end-to-end test would require a two-step
188 // (serialize-then-read) setup, which is left for a later phase.
189 
190 } // namespace
static uint64_t buf[ITERS]
volatile uint8_t status
Definition: cctlib.cpp:230
std::string find_newest(const std::string &dir, const std::string &prefix)
void cleanup(const std::string &dir, const std::string &prefix)
TEST_F(CCTLibIntegration, CctDataCentricClientTreeBasedRunsCleanly)
int run_pin(const std::string &tool, const std::vector< std::string > &args)
int a[1000]
Definition: testArray.c:5