xref: /aosp_15_r20/external/federated-compute/fcp/testing/testing.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2017 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "fcp/testing/testing.h"
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 
22 #include <filesystem>
23 #include <string>
24 
25 #include "gtest/gtest.h"
26 #include "absl/flags/flag.h"
27 #include "absl/status/status.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_replace.h"
30 #include "absl/strings/string_view.h"
31 #include "fcp/base/base_name.h"
32 #include "fcp/base/monitoring.h"
33 #include "fcp/base/platform.h"
34 #include "fcp/testing/tracing_schema.h"
35 #include "fcp/tracing/tracing_span.h"
36 
37 namespace fcp {
38 
TestName()39 std::string TestName() {
40   auto test_info = testing::UnitTest::GetInstance()->current_test_info();
41   return absl::StrReplaceAll(test_info->name(), {{"/", "_"}});
42 }
43 
TestCaseName()44 std::string TestCaseName() {
45   auto test_info = testing::UnitTest::GetInstance()->current_test_info();
46   return absl::StrReplaceAll(test_info->test_case_name(), {{"/", "_"}});
47 }
48 
GetTestDataPath(absl::string_view relative_path)49 std::string GetTestDataPath(absl::string_view relative_path) {
50   auto env = getenv("TEST_SRCDIR");
51   std::string test_srcdir = env ? env : "";
52   return ConcatPath(test_srcdir, ConcatPath("com_google_fcp", relative_path));
53 }
54 
TemporaryTestFile(absl::string_view suffix)55 std::string TemporaryTestFile(absl::string_view suffix) {
56   return ConcatPath(StripTrailingPathSeparator(testing::TempDir()),
57                     absl::StrCat(TestName(), suffix));
58 }
59 
60 namespace {
61 
EnsureDirExists(absl::string_view path)62 absl::Status EnsureDirExists(absl::string_view path) {
63   if (FileExists(path)) {
64     return absl::OkStatus();
65   }
66   auto path_str = std::string(path);
67   int error;
68 #ifndef _WIN32
69   error = mkdir(path_str.c_str(), 0733);
70 #else
71   error = _mkdir(path_str.c_str());
72 #endif
73   if (error) {
74     return absl::InternalError(absl::StrCat(
75         "cannot create directory ", path_str, "(error code ", error, ")"));
76   }
77   return absl::OkStatus();
78 }
79 
80 }  // namespace
81 
ShouldUpdateBaseline()82 bool ShouldUpdateBaseline() {
83   return getenv("FCP_UPDATE_BASELINE");
84 }
85 
86 namespace {
87 
MakeTempFileName()88 std::string MakeTempFileName() {
89 #ifdef __APPLE__
90 // Apple has marked tmpnam as deprecated. As we are compiling with -Werror,
91 // turning this off for this case. Apple recommends to use mkstemp instead,
92 // but because this opens a file, it's not exactly what we want, and it's not
93 // portable. std::filesystem in C++17 should fix this issue.
94 #pragma clang diagnostic push
95 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
96 #endif
97   return tmpnam(nullptr);
98 #ifdef __APPLE__
99 #pragma clang diagnostic pop
100 #endif
101 }
102 
ShellCommand(absl::string_view command,std::string * stdout_result,std::string * stderr_result)103 absl::Status ShellCommand(absl::string_view command, std::string* stdout_result,
104                           std::string* stderr_result) {
105 #ifdef _WIN32
106   return absl::UnimplementedError("ShellCommand not implemented for Windows");
107 #else
108   // Prepare command for output redirection.
109   std::string command_str = std::string(command);
110   std::string stdout_file;
111   if (stdout_result != nullptr) {
112     stdout_file = MakeTempFileName();
113     absl::StrAppend(&command_str, " 1>", stdout_file);
114   }
115   std::string stderr_file;
116   if (stderr_result != nullptr) {
117     stderr_file = MakeTempFileName();
118     absl::StrAppend(&command_str, " 2>", stderr_file);
119   }
120 
121   // Call the command.
122   int result = std::system(command_str.c_str());
123 
124   // Read and remove redirected output.
125   if (stdout_result != nullptr) {
126     auto status_or_result = ReadFileToString(stdout_file);
127     if (status_or_result.ok()) {
128       *stdout_result = status_or_result.value();
129       std::remove(stdout_file.c_str());
130     } else {
131       *stdout_result = "";
132     }
133   }
134   if (stderr_result != nullptr) {
135     auto status_or_result = ReadFileToString(stderr_file);
136     if (status_or_result.ok()) {
137       *stderr_result = status_or_result.value();
138       std::remove(stderr_file.c_str());
139     } else {
140       *stderr_result = "";
141     }
142   }
143 
144   // Construct result.
145   if (result != 0) {
146     return absl::InternalError(absl::StrCat(
147         "command execution failed: ", command_str, " returns ", result));
148   } else {
149     return absl::OkStatus();
150   }
151 #endif
152 }
153 
154 }  // namespace
155 
ComputeDiff(absl::string_view baseline_file,absl::string_view content)156 absl::StatusOr<std::string> ComputeDiff(absl::string_view baseline_file,
157                                         absl::string_view content) {
158   std::string diff_result;
159   std::string baseline_file_str = GetTestDataPath(baseline_file);
160   if (!FileExists(baseline_file_str)) {
161     diff_result = absl::StrCat("no recorded baseline file ", baseline_file_str);
162   } else {
163 #ifndef _WIN32
164     // Expect Unix diff command to be available.
165     auto provided_file = TemporaryTestFile(".provided");
166     auto status = WriteStringToFile(provided_file, content);
167     if (!status.ok()) {
168       return status;
169     }
170     std::string std_out, std_err;
171     status = ShellCommand(
172         absl::StrCat("diff -u ", baseline_file_str, " ", provided_file),
173         &std_out, &std_err);
174     std::remove(provided_file.c_str());
175     if (status.code() != OK) {
176       if (!std_err.empty()) {
177         // Indicates a failure in diff execution itself.
178         return absl::InternalError(absl::StrCat("command failed: ", std_err));
179       }
180       diff_result = std_out;
181     }
182 #else  // _WIN32
183     // For now we do a simple string compare on Windows.
184     auto status_or_string = ReadFileToString(baseline_file_str);
185     if (!status_or_string.ok()) {
186       return status_or_string.status();
187     }
188     if (status_or_string.value() != content) {
189       diff_result = "baseline and actual differ (see respective files)";
190     }
191 #endif
192   }
193   return diff_result;
194 }
195 
VerifyAgainstBaseline(absl::string_view baseline_file,absl::string_view content)196 StatusOr<std::string> VerifyAgainstBaseline(absl::string_view baseline_file,
197                                             absl::string_view content) {
198   auto status_or_diff_result = ComputeDiff(baseline_file, content);
199   if (!status_or_diff_result.ok()) {
200     return status_or_diff_result;
201   }
202   auto& diff_result = status_or_diff_result.value();
203   if (diff_result.empty()) {
204     // success
205     return status_or_diff_result;
206   }
207 
208   // Determine the location where to store the new baseline.
209   std::string new_baseline_file;
210   bool auto_update = false;
211 
212   if (new_baseline_file.empty() && ShouldUpdateBaseline()) {
213     new_baseline_file = GetTestDataPath(baseline_file);
214     diff_result =
215         absl::StrCat("\nAutomatically updated baseline file: ", baseline_file);
216     auto_update = true;
217   }
218 
219   if (new_baseline_file.empty()) {
220     // Store new baseline file in a TMP location.
221 #ifndef _WIN32
222     const char* temp_dir = "/tmp";
223 #else
224     const char* temp_dir = getenv("TEMP");
225 #endif
226     auto temp_output_dir =
227         ConcatPath(temp_dir, absl::StrCat("fcp_", TestCaseName()));
228     FCP_CHECK_STATUS(EnsureDirExists(temp_output_dir));
229     new_baseline_file = ConcatPath(temp_output_dir, BaseName(baseline_file));
230     absl::StrAppend(&diff_result, "\nNew baseline file: ", new_baseline_file);
231     absl::StrAppend(&diff_result, "\nTo update, use:");
232     absl::StrAppend(&diff_result, "\n\n cp ", new_baseline_file, " ",
233                     baseline_file, "\n");
234   }
235 
236   if (!auto_update) {
237     absl::StrAppend(&diff_result,
238                     "\nTo automatically update baseline files, use");
239     absl::StrAppend(&diff_result,
240                     "\nenvironment variable FCP_UPDATE_BASELINE.");
241   }
242 
243   // Write the new baseline.
244   auto status = WriteStringToFile(new_baseline_file, content);
245   if (!status.ok()) {
246     return status;
247   }
248 
249   // Deliver result.
250   if (auto_update) {
251     FCP_LOG(INFO) << diff_result;
252     diff_result = "";  // make test pass
253   }
254   return diff_result;
255 }
256 
IsCode(StatusCode code)257 StatusMatcher IsCode(StatusCode code) { return StatusMatcher(code); }
IsOk()258 StatusMatcher IsOk() { return IsCode(OK); }
259 
TraceTestError(SourceLocation loc)260 Error TraceTestError(SourceLocation loc) {
261   return TraceError<TestError>(loc.file_name(), loc.line());
262 }
263 
264 }  // namespace fcp
265