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
27namespace runtests {
28
29///////////////////////////////////////////////////////////////////////////////
30// HELPER CLASSES
31///////////////////////////////////////////////////////////////////////////////
32
33ScopedScriptFile::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
46ScopedScriptFile::~ScopedScriptFile() {
47    remove(path_.data());
48}
49
50fbl::StringPiece ScopedScriptFile::path() const {
51    return path_;
52}
53
54ScopedTestFile::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
73ScopedTestFile::~ScopedTestFile() {
74    remove(path_.data());
75}
76
77fbl::StringPiece ScopedTestFile::path() const {
78    return path_;
79}
80
81int 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.
88int 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|.
109bool 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.
126bool 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
163namespace {
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.
167bool 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
205BEGIN_TEST_CASE(TestHelpers)
206RUN_TEST(ScopedDirsAndFilesAreIndeedScoped)
207END_TEST_CASE(TestHelpers)
208
209} // namespace
210} // namespace runtests
211