1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <dirent.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13
14 #include <fbl/auto_call.h>
15 #include <fbl/string.h>
16 #include <fbl/string_buffer.h>
17 #include <fbl/unique_fd.h>
18 #include <fbl/unique_ptr.h>
19 #include <fbl/vector.h>
20 #include <runtests-utils/runtests-utils.h>
21 #include <unittest/unittest.h>
22
23 #include "runtests-utils-test-globals.h"
24 #include "runtests-utils-test-utils.h"
25
26
27 namespace runtests {
28
29 ///////////////////////////////////////////////////////////////////////////////
30 // HELPER CLASSES
31 ///////////////////////////////////////////////////////////////////////////////
32
ScopedScriptFile(const fbl::StringPiece path,const fbl::StringPiece contents)33 ScopedScriptFile::ScopedScriptFile(const fbl::StringPiece path,
34 const fbl::StringPiece contents)
35 : path_(path) {
36 const int fd = open(path_.data(), O_CREAT | O_WRONLY, S_IRWXU);
37 ZX_ASSERT_MSG(-1 != fd, "%s", strerror(errno));
38 ZX_ASSERT(
39 sizeof(kScriptShebang) ==
40 static_cast<size_t>(write(fd, kScriptShebang, sizeof(kScriptShebang))));
41 ZX_ASSERT(contents.size() ==
42 static_cast<size_t>(write(fd, contents.data(), contents.size())));
43 ZX_ASSERT_MSG(-1 != close(fd), "%s", strerror(errno));
44 }
45
~ScopedScriptFile()46 ScopedScriptFile::~ScopedScriptFile() {
47 remove(path_.data());
48 }
49
path() const50 fbl::StringPiece ScopedScriptFile::path() const {
51 return path_;
52 }
53
ScopedTestFile(const fbl::StringPiece path,const fbl::StringPiece file)54 ScopedTestFile::ScopedTestFile(
55 const fbl::StringPiece path, const fbl::StringPiece file)
56 : path_(path) {
57 fbl::unique_fd input_fd{open(file.data(), O_RDONLY)};
58 ZX_ASSERT_MSG(input_fd, "%s", strerror(errno));
59
60 fbl::unique_fd output_fd{open(path_.data(), O_CREAT | O_WRONLY, S_IRWXU)};
61 ZX_ASSERT_MSG(output_fd, "%s", strerror(errno));
62
63 constexpr size_t kBufSize = 1024;
64
65 char buf[kBufSize];
66 ssize_t n;
67 while ((n = read(input_fd.get(), buf, kBufSize)) > 0) {
68 ZX_ASSERT_MSG(write(output_fd.get(), buf, n) == n, "write failed: %s", strerror(errno));
69 }
70 ZX_ASSERT_MSG(n != -1, "read failed: %s", strerror(errno));
71 }
72
~ScopedTestFile()73 ScopedTestFile::~ScopedTestFile() {
74 remove(path_.data());
75 }
76
path() const77 fbl::StringPiece ScopedTestFile::path() const {
78 return path_;
79 }
80
81 int ScopedTestDir::num_test_dirs_created_ = 0;
82
83 ///////////////////////////////////////////////////////////////////////////////
84 // FILE I/O HELPERS
85 ///////////////////////////////////////////////////////////////////////////////
86
87 // Returns the number of files or subdirectories in a given directory.
NumEntriesInDir(const char * dir_path)88 int NumEntriesInDir(const char* dir_path) {
89 struct dirent* entry;
90 int num_entries = 0;
91 DIR* dp;
92
93 if (!(dp = opendir(dir_path))) {
94 // dir_path actually points to a file. Return -1 by convention.
95 return -1;
96 }
97 while ((entry = readdir(dp))) {
98 // Skip "." and "..".
99 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
100 continue;
101 }
102 ++num_entries;
103 }
104 closedir(dp);
105 return num_entries;
106 }
107
108 // Returns true if and only if the contents of |file| match |expected|.
CompareFileContents(FILE * file,const char * expected)109 bool CompareFileContents(FILE* file, const char* expected) {
110 BEGIN_HELPER;
111 // Get the size of the file contents, copy it into a buffer, and compare.
112 ASSERT_EQ(0, fseek(file, 0, SEEK_END));
113 const long unsigned int size = ftell(file);
114 rewind(file);
115 fbl::unique_ptr<char[]> buf(new char[size + 1]);
116 buf[size] = 0;
117 ASSERT_EQ(size, fread(buf.get(), sizeof(char), size, file));
118 EXPECT_STR_EQ(expected, buf.get());
119 END_HELPER;
120 }
121
122 // Computes the relative path within |output_dir| of the output file of the
123 // test at |test_path|, setting |output_file_rel_path| as its value if
124 // successful.
125 // Returns true iff successful.
GetOutputFileRelPath(const fbl::StringPiece & output_dir,const fbl::StringPiece & test_path,fbl::String * output_file_rel_path)126 bool GetOutputFileRelPath(const fbl::StringPiece& output_dir,
127 const fbl::StringPiece& test_path,
128 fbl::String* output_file_rel_path) {
129 if (output_file_rel_path == nullptr) {
130 printf("FAILURE: |output_file_rel_path| was null.");
131 return false;
132 }
133 fbl::String dir_of_test_output = JoinPath(output_dir, test_path);
134 DIR* dp = opendir(dir_of_test_output.c_str());
135 if (dp == nullptr) {
136 printf("FAILURE: could not open directory: %s\n", dir_of_test_output.c_str());
137 return false;
138 }
139 struct dirent* entry;
140 int num_entries = 0;
141 fbl::String output_file_name;
142 while ((entry = readdir(dp))) {
143 // Skip "." and "..".
144 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
145 continue;
146 }
147 if (entry->d_type != DT_REG) {
148 continue;
149 }
150 output_file_name = fbl::String(entry->d_name);
151 ++num_entries;
152 }
153 closedir(dp);
154 *output_file_rel_path = JoinPath(test_path, output_file_name);
155 if (num_entries != 1) {
156 printf("FAILURE: there are %d entries in %s. There should only be a "
157 "single output file\n",
158 num_entries, dir_of_test_output.c_str());
159 }
160 return num_entries == 1;
161 }
162
163 namespace {
164
165 // This ensures that ScopedTestDir and ScopedScriptFile, which we make heavy
166 // use of in these tests, are indeed scoped and tear down without error.
ScopedDirsAndFilesAreIndeedScoped()167 bool ScopedDirsAndFilesAreIndeedScoped() {
168 BEGIN_TEST;
169
170 // Entering a test case, test_dir.path() should be empty.
171 EXPECT_EQ(0, NumEntriesInDir(TestFsRoot()));
172
173 {
174 ScopedTestDir dir;
175 EXPECT_EQ(1, NumEntriesInDir(TestFsRoot()));
176 EXPECT_EQ(0, NumEntriesInDir(dir.path()));
177 {
178 fbl::String file_name1 = JoinPath(dir.path(), "a.sh");
179 ScopedScriptFile file1(file_name1, "A");
180 EXPECT_EQ(1, NumEntriesInDir(dir.path()));
181 {
182 fbl::String file_name2 = JoinPath(dir.path(), "b.sh");
183 ScopedScriptFile file2(file_name2, "B");
184 EXPECT_EQ(2, NumEntriesInDir(dir.path()));
185 }
186 EXPECT_EQ(1, NumEntriesInDir(dir.path()));
187 }
188 EXPECT_EQ(0, NumEntriesInDir(dir.path()));
189 }
190
191 EXPECT_EQ(0, NumEntriesInDir(TestFsRoot()));
192
193 {
194 ScopedTestDir dir1;
195 ScopedTestDir dir2;
196 ScopedTestDir dir3;
197 EXPECT_EQ(3, NumEntriesInDir(TestFsRoot()));
198 }
199
200 EXPECT_EQ(0, NumEntriesInDir(TestFsRoot()));
201
202 END_TEST;
203 }
204
205 BEGIN_TEST_CASE(TestHelpers)
206 RUN_TEST(ScopedDirsAndFilesAreIndeedScoped)
207 END_TEST_CASE(TestHelpers)
208
209 } // namespace
210 } // namespace runtests
211