blob: bfc604a5965f830f0e779c30f84616da12229ee2 [file] [log] [blame] [edit]
/*
* Copyright (c) 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* NaCl tests for simple syscalls
*/
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "native_client/src/include/build_config.h"
#include "native_client/src/include/nacl_assert.h"
#include "native_client/src/trusted/service_runtime/nacl_config.h"
#define PRINT_HEADER 0
#define TEXT_LINE_SIZE 1024
bool isWindows = false;
/*
* TODO(sbc): remove this test once these declarations get added to the
* newlib toolchain
*/
#ifndef __GLIBC__
extern "C" {
int gethostname(char *name, size_t len);
int eaccess(const char *pathname, int mode);
int fchdir(int fd);
int fchmod(int fd, mode_t mode);
int fsync(int fd);
int fdatasync(int fd);
int ftruncate(int fd, off_t length);
int utimes(const char *filename, const struct timeval *times);
}
#endif
/*
* function failed(testname, msg)
* print failure message and exit with a return code of -1
*/
bool failed(const char *testname, const char *msg) {
printf("TEST FAILED: %s: %s\n", testname, msg);
return false;
}
/*
* function passed(testname, msg)
* print success message
*/
bool passed(const char *testname, const char *msg) {
printf("TEST PASSED: %s: %s\n", testname, msg);
return true;
}
/*
* Split filename into basename and dirname.
* The argument is modified in place such that it
* becomes the dirname and the basename is returned.
*/
static char *split_name(char *full_name) {
// Strip off the trailing filename
char *basename = strrchr(full_name, '/');
if (basename == NULL) {
basename = strrchr(full_name, '\\');
ASSERT_NE_MSG(basename, NULL, "test_file contains no dir separator");
if (!basename)
return NULL;
}
basename[0] = '\0';
return basename + 1;
}
// In case a previous run of this test failed and left the file behind,
// remove the file first.
// TODO(mseaborn): It would be cleaner to create a guaranteed-empty temp
// directory instead of doing this.
static void ensure_file_is_absent(const char *filename) {
int result = unlink(filename);
if (result != 0) {
ASSERT_EQ(errno, ENOENT);
}
}
/*
* function test*()
*
* Simple tests follow below. Each test may call one or more
* of the functions above. They all have a boolean return value
* to indicate success (all tests passed) or failure (one or more
* tests failed) Order matters - the parent should call
* test1() before test2(), and so on.
*/
bool test_sched_yield() {
if (sched_yield()) {
printf("sched_yield failed\n");
return false;
}
return true;
}
bool test_sysconf() {
// TODO(hamaji): Implement _SC_NPROCESSORS_ONLN for newlib based
// non-SFI mode. Note that this test works for unsandboxed mode.
if (NONSFI_MODE)
return true;
int rv;
rv = sysconf(_SC_NPROCESSORS_ONLN);
ASSERT_GE(rv, 1);
// test sysconf on an invalid input.
rv = sysconf(-1);
ASSERT_EQ(rv, -1);
return true;
}
// Simple test that chdir returns zero for '.'. chdir gets more
// significant testing as part of the getcwd test.
bool test_chdir() {
int rtn = chdir(".");
ASSERT_EQ_MSG(rtn, 0, "chdir() failed");
return passed("test_chdir", "all");
}
bool test_fchdir() {
// TODO(smklein): Re-enable when nonsfi mode translates O_* values.
// See "test_open_directory" test for more details.
#if defined(__arm__) || defined(__pnacl__)
if (NONSFI_MODE)
return true;
#endif
char dirname[PATH_MAX] = { '\0' };
char newdir[PATH_MAX] = { '\0' };
char parent[PATH_MAX] = { '\0' };
int retcode;
int fd;
char *rtn = getcwd(dirname, PATH_MAX);
ASSERT_EQ_MSG(rtn, dirname, "getcwd() failed");
// Calculate parent directory.
strncpy(parent, dirname, PATH_MAX);
char *basename_start;
if (isWindows) {
basename_start = strrchr(parent, '\\');
} else {
basename_start = strrchr(parent, '/');
}
if (basename_start == NULL) {
basename_start = strrchr(parent, '\\');
ASSERT_NE_MSG(basename_start, NULL, "test_file contains no dir separator");
}
basename_start[0] = '\0';
// Open parent directory
fd = open(parent, O_RDONLY | O_DIRECTORY);
ASSERT_MSG(fd >= 0, "open() failed to open parent directory");
retcode = fchdir(fd);
if (isWindows) {
// TODO(smklein) Update this once fchdir is implemented.
ASSERT_NE_MSG(retcode, 0, "fchdir() should have failed");
return passed("test_fchdir", "all");
} else {
ASSERT_EQ_MSG(retcode, 0, "fchdir() failed");
}
rtn = getcwd(newdir, PATH_MAX);
ASSERT_EQ_MSG(rtn, newdir, "getcwd() failed");
ASSERT_MSG(strcmp(newdir, parent) == 0, "getcwd() failed to change cwd");
ASSERT_EQ_MSG(close(fd), 0, "close() failed");
// Let's go back to the child directory
fd = open(dirname, O_RDONLY | O_DIRECTORY);
ASSERT_NE_MSG(fd, -1, "open() failed to open child directory");
retcode = fchdir(fd);
ASSERT_EQ_MSG(retcode, 0, "fchdir() failed");
ASSERT_EQ_MSG(close(fd), 0, "close() failed");
rtn = getcwd(newdir, PATH_MAX);
ASSERT_EQ_MSG(rtn, newdir, "getcwd() failed");
ASSERT_MSG(strcmp(newdir, dirname) == 0, "getcwd() failed to change cwd");
return passed("test fchdir", "all");
}
bool test_mkdir_rmdir(const char *test_file) {
// Use a temporary directory name alongside the test_file which
// was passed in.
char dirname[PATH_MAX];
strncpy(dirname, test_file, PATH_MAX);
split_name(dirname);
ASSERT(strlen(dirname) + 6 < PATH_MAX);
strncat(dirname, "tmpdir", 6);
// Attempt to remove the directory in case it already exists
// from a previous test run.
if (rmdir(dirname) != 0)
ASSERT_EQ_MSG(errno, ENOENT, "rmdir() failed to cleanup existing dir");
char cwd[PATH_MAX];
char *cwd_rtn = getcwd(cwd, PATH_MAX);
ASSERT_EQ_MSG(cwd_rtn, cwd, "getcwd() failed");
int rtn = mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR);
ASSERT_EQ_MSG(rtn, 0, "mkdir() failed");
rtn = rmdir(dirname);
ASSERT_EQ_MSG(rtn, 0, "rmdir() failed");
rtn = rmdir("This file does not exist");
ASSERT_EQ_MSG(rtn, -1, "rmdir() failed to fail");
ASSERT_EQ_MSG(errno, ENOENT, "rmdir() failed to generate ENOENT");
return passed("test_mkdir_rmdir", "all");
}
bool test_getcwd() {
char dirname[PATH_MAX] = { '\0' };
char newdir[PATH_MAX] = { '\0' };
char parent[PATH_MAX] = { '\0' };
char *rtn = getcwd(dirname, PATH_MAX);
ASSERT_EQ_MSG(rtn, dirname, "getcwd() failed to return dirname");
ASSERT_NE_MSG(strlen(dirname), 0, "getcwd() failed to set valid dirname");
// Call with size == 0 and buf == NULL should return a malloc'd buffer
char *rtn2 = getcwd(NULL, 0);
ASSERT_NE(rtn2, NULL);
ASSERT_EQ(strcmp(rtn, rtn2), 0);
free(rtn2);
// Call with buf == NULL and non-zero size should return a malloc'd buffer
// that is 'size' bytes long.
rtn2 = getcwd(NULL, PATH_MAX*2);
ASSERT_NE(rtn2, NULL);
ASSERT_EQ(strcmp(rtn, rtn2), 0);
// Overwrite all bytes in the allocation. We have no way to verify that
// the allocation really is this big but memory sanitisers should fail here
// if it's not.
memset(rtn2, 0xba, PATH_MAX*2);
free(rtn2);
// Call with size == 0 and buf != NULL should fail (with EINVAL)
rtn = getcwd(dirname, 0);
ASSERT_EQ(rtn, NULL);
ASSERT_EQ(errno, EINVAL);
// Check that when size is too small ERANGE gets set
rtn = getcwd(dirname, 1);
ASSERT_EQ(rtn, NULL);
ASSERT_EQ(errno, ERANGE);
// Calculate parent directory.
strncpy(parent, dirname, PATH_MAX);
char *basename_start = strrchr(parent, '/');
if (basename_start == NULL) {
basename_start = strrchr(parent, '\\');
ASSERT_NE_MSG(basename_start, NULL, "test_file contains no dir separator");
}
basename_start[0] = '\0';
int retcode = chdir("..");
ASSERT_EQ_MSG(retcode, 0, "chdir() failed");
rtn = getcwd(newdir, PATH_MAX);
ASSERT_EQ_MSG(rtn, newdir, "getcwd() failed");
ASSERT_MSG(strcmp(newdir, parent) == 0, "getcwd() failed after chdir");
retcode = chdir(dirname);
ASSERT_EQ_MSG(retcode, 0, "chdir() failed");
rtn = getcwd(dirname, 2);
ASSERT_EQ_MSG(rtn, NULL, "getcwd() failed to fail");
ASSERT_EQ_MSG(errno, ERANGE, "getcwd() failed to generate ERANGE");
return passed("test_getcwd", "all");
}
bool test_unlink(const char *test_file) {
int rtn;
struct stat buf;
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_unlink", test_file);
temp_file[PATH_MAX - 1] = '\0';
int fd = open(temp_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
ASSERT_MSG(fd >= 0, "open() failed");
rtn = close(fd);
ASSERT_EQ_MSG(rtn, 0, "close() failed");
rtn = stat(temp_file, &buf);
ASSERT_EQ_MSG(rtn, 0, "stat() failed");
rtn = unlink(temp_file);
ASSERT_EQ_MSG(rtn, 0, "unlink() failed");
rtn = stat(temp_file, &buf);
ASSERT_NE_MSG(rtn, 0, "unlink() failed to remove file");
rtn = unlink(temp_file);
ASSERT_NE_MSG(rtn, 0, "unlink() failed to fail");
return passed("test_unlink", "all");
}
bool test_rename(const char *test_file) {
int rtn;
struct stat buf;
char filename1[PATH_MAX];
char filename2[PATH_MAX];
snprintf(filename1, PATH_MAX, "%s.tmp1", test_file);
snprintf(filename2, PATH_MAX, "%s.tmp2", test_file);
// Create a test file and verify that we can stat() it.
int fd = open(filename1, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
ASSERT_MSG(fd >= 0, "open() failed");
rtn = close(fd);
ASSERT_EQ_MSG(rtn, 0, "close() failed");
rtn = stat(filename1, &buf);
ASSERT_EQ_MSG(rtn, 0, "stat() failed");
// Rename the test file and verify that the old file is no
// longer stat()-able.
rtn = rename(filename1, filename2);
ASSERT_EQ_MSG(rtn, 0, "rename() failed");
rtn = stat(filename2, &buf);
ASSERT_EQ_MSG(rtn, 0, "stat() of new file failed");
rtn = stat(filename1, &buf);
ASSERT_NE_MSG(rtn, 0, "stat() of old name should fail after rename");
ASSERT_EQ(remove(filename2), 0);
return passed("test_rename", "all");
}
bool test_link(const char *test_file) {
struct stat buf;
struct stat buf_orig;
char link_filename[PATH_MAX];
char target_filename[PATH_MAX];
snprintf(target_filename, PATH_MAX, "%s.target", test_file);
snprintf(link_filename, PATH_MAX, "%s.link", test_file);
ensure_file_is_absent(target_filename);
ensure_file_is_absent(link_filename);
// Create link target with some dummy data
int fd = open(target_filename, O_WRONLY | O_CREAT, S_IRWXU);
ASSERT(fd >= 0);
ASSERT_EQ(write(fd, "123", 3), 3);
ASSERT_EQ(close(fd), 0);
int rtn = link(target_filename, link_filename);
if (rtn != 0 && errno == ENOSYS && isWindows) {
// If we get ENOSYS, assume we are on Windows, where link() is expected
// to fail.
return passed("test_link", "all");
}
ASSERT_EQ(rtn, 0);
// Verify that the new file is a regular file and that changes to it are
// mirrored in the original file.
ASSERT_EQ(stat(link_filename, &buf), 0);
ASSERT_EQ(stat(target_filename, &buf_orig), 0);
ASSERT(S_ISREG(buf.st_mode));
ASSERT(S_ISREG(buf_orig.st_mode));
ASSERT_EQ(buf_orig.st_size, 3);
ASSERT_EQ(buf_orig.st_size, buf.st_size);
// Write some bytes to the new file
fd = open(link_filename, O_WRONLY);
ASSERT(fd >= 0);
ASSERT_EQ(write(fd, "test", 4), 4);
ASSERT_EQ(close(fd), 0);
// Verify that the two files are still the same size
ASSERT_EQ(stat(link_filename, &buf), 0);
ASSERT_EQ(stat(target_filename, &buf_orig), 0);
ASSERT(S_ISREG(buf.st_mode));
ASSERT(S_ISREG(buf_orig.st_mode));
ASSERT_EQ(buf_orig.st_size, buf.st_size);
ASSERT_EQ(buf_orig.st_size, 4);
ASSERT_EQ(remove(link_filename), 0);
ASSERT_EQ(remove(target_filename), 0);
return passed("test_link", "all");
}
// This tests symlink/readlink and lstat.
bool test_symlinks(const char *test_file) {
char dirname[PATH_MAX];
char link_filename[PATH_MAX];
struct stat buf;
// Test that lstat of the test_file works
ASSERT_EQ(lstat(test_file, &buf), 0);
ASSERT_EQ(S_ISREG(buf.st_mode), 1);
// Split filename into basename and dirname.
strncpy(dirname, test_file, PATH_MAX);
char *basename = split_name(dirname);
snprintf(link_filename, PATH_MAX, "%s.link", test_file);
ensure_file_is_absent(link_filename);
// Create this link
int rtn = symlink(basename, link_filename);
if (rtn != 0 && errno == ENOSYS && isWindows) {
// If we get ENOSYS, assume we are on Windows, where symlink() and
// readlink() are expected to fail.
return passed("test_symlinks", "all");
}
ASSERT_EQ(rtn, 0);
// Check the lstat() and stat of the link
ASSERT_EQ(lstat(link_filename, &buf), 0);
ASSERT_NE_MSG(S_ISLNK(buf.st_mode), 0, "lstat of link failed to be ISLNK");
ASSERT_EQ_MSG(S_ISREG(buf.st_mode), 0, "lstat of link should not be ISREG");
ASSERT_EQ(stat(link_filename, &buf), 0);
ASSERT_EQ_MSG(S_ISLNK(buf.st_mode), 0, "stat of symlink should not ISLNK");
ASSERT_NE_MSG(S_ISREG(buf.st_mode), 0, "stat of symlink should report ISREG");
// Test readlink().
char link_dest[PATH_MAX];
memset(link_dest, 0x77, sizeof(link_dest));
ssize_t result = readlink(link_filename, link_dest, sizeof(link_dest));
ASSERT_EQ(result, (ssize_t) strlen(basename));
ASSERT_EQ(memcmp(link_dest, basename, result), 0);
// readlink() should not write a null terminator.
ASSERT_EQ(link_dest[result], 0x77);
// Test readlink() with a truncated result.
memset(link_dest, 0x77, sizeof(link_dest));
result = readlink(link_filename, link_dest, 1);
ASSERT_EQ(result, 1);
ASSERT_EQ(link_dest[0], basename[0]);
// The rest of the buffer should not be modified.
for (size_t i = 1; i < sizeof(link_dest); ++i)
ASSERT_EQ(link_dest[i], 0x77);
// calling symlink again should yield EEXIST.
ASSERT_EQ(symlink(test_file, link_filename), -1);
ASSERT_EQ(errno, EEXIST);
ASSERT_EQ(remove(link_filename), 0);
return passed("test_symlinks", "all");
}
bool test_chmod(const char *test_file) {
struct stat buf;
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_chmod", test_file);
ensure_file_is_absent(temp_file);
int fd = open(temp_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(close(fd), 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_mode & ~S_IFMT, S_IRUSR | S_IWUSR);
// change the file to readonly and verify the change
ASSERT_EQ(chmod(temp_file, S_IRUSR), 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_mode & ~S_IFMT, S_IRUSR);
ASSERT(open(temp_file, O_WRONLY) < 0);
ASSERT_EQ(remove(temp_file), 0);
return passed("test_chmod", "all");
}
bool test_fchmod(const char *test_file) {
struct stat buf;
char temp_file[PATH_MAX];
int retcode;
snprintf(temp_file, PATH_MAX, "%s.tmp_fchmod", test_file);
ensure_file_is_absent(temp_file);
int fd = open(temp_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_mode & ~S_IFMT, S_IRUSR | S_IWUSR);
// change the file to readonly and verify the change
retcode = fchmod(fd, S_IRUSR);
if (isWindows) {
// TODO(smklein) Update this once fchmod is implemented.
ASSERT_NE_MSG(retcode, 0, "fchmod() should have failed");
} else {
ASSERT_EQ(retcode, 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_mode & ~S_IFMT, S_IRUSR);
ASSERT(open(temp_file, O_WRONLY) < 0);
}
ASSERT_EQ(close(fd), 0);
ASSERT_EQ(remove(temp_file), 0);
return passed("test_fchmod", "all");
}
static void test_access_call(const char *path, int mode, int expected_result) {
ASSERT_EQ(access(path, mode), expected_result);
ASSERT_EQ(eaccess(path, mode), expected_result);
}
bool test_access(const char *test_file) {
char temp_access[PATH_MAX];
snprintf(temp_access, PATH_MAX, "%s.tmp_access", test_file);
test_access_call(test_file, F_OK, 0);
test_access_call(test_file, R_OK, 0);
test_access_call(test_file, W_OK, 0);
test_access_call(temp_access, F_OK, -1);
test_access_call(temp_access, R_OK, -1);
test_access_call(temp_access, W_OK, -1);
/*
* We can't test the X bit here since it's not consistent across platforms.
* On win32 there is no equivalent so we always return true. On Mac/Linux
* the underlying X bit is reported.
*/
// test_access_call(test_file, X_OK, -1);
// test_access_call(temp_access, X_OK, -1);
// Create a read-only file
int fd = open(temp_access, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR);
ASSERT(fd > 0);
ASSERT_EQ(close(fd), 0);
test_access_call(temp_access, F_OK, 0);
test_access_call(temp_access, R_OK, 0);
test_access_call(temp_access, W_OK, -1);
ASSERT_EQ(remove(temp_access), 0);
return passed("test_access", "all");
}
bool test_utimes(const char *test_file) {
// TODO(mseaborn): Implement utimes for unsandboxed mode.
if (NONSFI_MODE)
return true;
// To avoid going back in time (problematic for Windows), grab a future time
// which (usually) matches our DST state. This also minimizes the risk of a
// bug (also found on Windows) where the result of stat is inaccurate if the
// current and target time are in different Daylight Savings Time states.
const time_t current_time = time(NULL);
static const time_t SEC_PER_HOUR = 60 * 60;
const time_t a_sec = current_time + SEC_PER_HOUR;
const time_t m_sec = current_time + SEC_PER_HOUR + 1;
struct timeval times[2] = {
{a_sec, 0},
{m_sec, 0}
};
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_utimes", test_file);
ensure_file_is_absent(temp_file);
int fd = open(temp_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(close(fd), 0);
// Use the times we generated earlier (and check the second resolution)
ASSERT_EQ(utimes(temp_file, times), 0);
// Verify the actime + modtime.
struct stat s;
ASSERT_EQ(stat(temp_file, &s), 0);
ASSERT_EQ(s.st_atime, a_sec);
ASSERT_EQ(s.st_mtime, m_sec);
// "NULL" should be a valid time.
ASSERT_EQ(utimes(temp_file, NULL), 0);
ASSERT_EQ(stat(temp_file, &s), 0);
ASSERT_NE(s.st_atime, a_sec);
ASSERT_NE(s.st_mtime, m_sec);
return passed("test_utimes", "all");
}
bool test_fsync(const char *test_file) {
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_fsync", test_file);
char buffer[100];
for (size_t i = 0; i < sizeof(buffer); i++)
buffer[i] = i;
// Write 100 sequential chars to the test file.
int fd = open(temp_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(100, write(fd, buffer, 100));
// For now, only testing that fsync does not return an error.
// TODO(smklein): This test is weak -- improve it.
ASSERT_EQ(0, fsync(fd));
ASSERT_EQ(0, close(fd));
return passed("test_fsync", "all");
}
bool test_fdatasync(const char *test_file) {
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_fdatasync", test_file);
char buffer[100];
for (size_t i = 0; i < sizeof(buffer); i++)
buffer[i] = i;
// Write 100 sequential chars to the test file.
int fd = open(temp_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(100, write(fd, buffer, 100));
// For now, only testing that fdatasync does not return an error.
// TODO(smklein): This test is weak -- improve it.
ASSERT_EQ(0, fdatasync(fd));
ASSERT_EQ(0, close(fd));
return passed("test_fdatasync", "all");
}
bool test_truncate(const char *test_file) {
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_truncate", test_file);
char buffer[100];
char read_buffer[200];
struct stat buf;
for (size_t i = 0; i < sizeof(buffer); i++)
buffer[i] = i;
// Write 100 sequential chars to the test file.
int fd = open(temp_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(100, write(fd, buffer, 100));
ASSERT_EQ(0, close(fd));
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 100);
// truncate the file beyond its current length
ASSERT_EQ(truncate(temp_file, 200), 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 200);
// Verify the new content, which should be 100
// bytes of sequential chars and 100 bytes of '\0'
fd = open(temp_file, O_RDONLY);
ASSERT(fd >= 0);
ASSERT_EQ(read(fd, read_buffer, 200), 200);
ASSERT_EQ(memcmp(read_buffer, buffer, 100), 0);
for (int i = 100; i < 200; i++)
ASSERT_EQ(read_buffer[i], 0);
ASSERT_EQ(0, close(fd));
// Now truncate the file to a size smaller than the
// original
ASSERT_EQ(truncate(temp_file, 50), 0);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 50);
fd = open(temp_file, O_RDONLY);
ASSERT(fd >= 0);
ASSERT_EQ(read(fd, read_buffer, 50), 50);
ASSERT_EQ(memcmp(read_buffer, buffer, 50), 0);
ASSERT_EQ(0, close(fd));
ASSERT_EQ(remove(temp_file), 0);
return passed("test_truncate", "all");
}
bool test_ftruncate(const char *test_file) {
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_ftruncate", test_file);
char buffer[100];
char read_buffer[200];
struct stat buf;
for (size_t i = 0; i < sizeof(buffer); i++)
buffer[i] = i;
// Write 100 sequential chars to the test file.
int fd = open(temp_file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT(fd >= 0);
ASSERT_EQ(100, write(fd, buffer, 100));
ASSERT_EQ(0, close(fd));
fd = open(temp_file, O_WRONLY, S_IRUSR | S_IWUSR);
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 100);
// ftruncate the file beyond its current length
ASSERT_EQ(ftruncate(fd, 200), 0);
ASSERT_EQ(0, close(fd));
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 200);
// Verify the new content, which should not be 100
// bytes of sequential chars and 100 bytes of '\0'
fd = open(temp_file, O_RDWR);
ASSERT(fd >= 0);
ASSERT_EQ(read(fd, read_buffer, 200), 200);
ASSERT_EQ(memcmp(read_buffer, buffer, 100), 0);
for (int i = 100; i < 200; i++)
ASSERT_EQ(read_buffer[i], 0);
// Now ftruncate the file to a size smaller than the
// original
ASSERT_EQ(ftruncate(fd, 50), 0);
ASSERT_EQ(0, close(fd));
ASSERT_EQ(stat(temp_file, &buf), 0);
ASSERT_EQ(buf.st_size, 50);
fd = open(temp_file, O_RDONLY);
ASSERT(fd >= 0);
ASSERT_EQ(read(fd, read_buffer, 50), 50);
ASSERT_EQ(memcmp(read_buffer, buffer, 50), 0);
ASSERT_EQ(0, close(fd));
ASSERT_EQ(remove(temp_file), 0);
return passed("test_ftruncate", "all");
}
bool test_open_trunc(const char *test_file) {
int fd;
char buffer[100];
struct stat buf;
const char *testname = "test_open_trunc";
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.open_truncate", test_file);
// Write some data to a new file.
fd = open(temp_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT_NE(-1, fd);
memset(buffer, 0, 100);
ASSERT_EQ(100, write(fd, buffer, 100));
ASSERT_EQ(0, close(fd));
// Verify size is 100.
ASSERT_EQ(0, stat(temp_file, &buf));
ASSERT_EQ(100, buf.st_size);
// Open the file without O_TRUNC
fd = open(temp_file, O_RDWR);
ASSERT_NE(-1, fd);
ASSERT_EQ(0, close(fd));
// Verify size is still 100.
ASSERT_EQ(0, stat(temp_file, &buf));
ASSERT_EQ(100, buf.st_size);
// Open the file again with O_TRUNC.
fd = open(temp_file, O_RDWR | O_TRUNC);
ASSERT_NE(-1, fd);
ASSERT_EQ(0, close(fd));
// Verify size is now 0.
ASSERT_EQ(0, stat(temp_file, &buf));
ASSERT_EQ(0, buf.st_size);
return passed(testname, "all");
}
// O_DIRECTORY tells open to insist that it's a directory.
bool test_open_directory(const char *test_file) {
// Currently nonsfi mode does no translation (or validation) of the O_*
// flag values for open; on Linux/ARM, the O_DIRECTORY value does not
// match the NaCl value used here, so this doesn't do the right thing.
//
// TODO(mcgrathr): Re-enable when nonsfi mode translates O_* values
#if defined(__arm__) || defined(__pnacl__)
if (NONSFI_MODE)
return true;
#endif
const char *testname = "test_open_directory";
int fd;
// file exists and is not a directory
fd = open(test_file, O_RDONLY | O_DIRECTORY);
ASSERT_EQ_MSG(fd, -1, "open(test_file, O_RDONLY | O_DIRECTORY)");
ASSERT_EQ(errno, ENOTDIR);
// directory OK
fd = open(".", O_RDONLY | O_DIRECTORY);
ASSERT_NE_MSG(fd, -1, "open(., O_RDONLY | O_DIRECTORY)");
close(fd);
return passed(testname, "all");
}
// open() returns the new file descriptor, or -1 if an error occurred
bool test_open(const char *test_file) {
int fd;
const char *testname = "test_open";
// file OK, flag OK
fd = open(test_file, O_RDONLY);
ASSERT_NE_MSG(fd, -1, "open(test_file, O_RDONLY)");
close(fd);
errno = 0;
// file does not exist, flags OK
fd = open("testdata/file_none.txt", O_RDONLY);
ASSERT_EQ_MSG(fd, -1, "open(testdata/file_none.txt, O_RDONLY)");
// no such file or directory
ASSERT_EQ(errno, ENOENT);
// file OK, flags OK, mode OK
fd = open(test_file, O_WRONLY, S_IRUSR);
ASSERT_NE_MSG(fd, -1, "open(test_file, O_WRONLY, S_IRUSR)");
close(fd);
// too many args
fd = open(test_file, O_RDWR, S_IRUSR, O_APPEND);
ASSERT_NE_MSG(fd, -1, "open(test_file, O_RDWR, S_IRUSR, O_APPEND)");
close(fd);
// directory OK
fd = open(".", O_RDONLY);
ASSERT_NE_MSG(fd, -1, "open(., O_RDONLY)");
close(fd);
// open with O_CREAT | O_EXCL should fail on existing directories.
fd = open(".", O_RDONLY | O_CREAT | O_EXCL);
ASSERT_EQ_MSG(fd, -1, "open(., O_RDONLY | O_EXCL)");
errno = 0;
// directory does not exist
fd = open("nosuchdir", O_RDONLY);
ASSERT_EQ_MSG(fd, -1, "open(nosuchdir, O_RDONLY)");
// no such file or directory
ASSERT_EQ(errno, ENOENT);
// open exiting file with O_CREAT should succeed
fd = open(test_file, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT_NE_MSG(fd, -1, "open(test_file, O_RDONLY | O_CREAT)");
// open exiting file with O_CREAT | O_EXCL should fail
fd = open(test_file, O_RDONLY | O_CREAT | O_EXCL);
ASSERT_EQ_MSG(fd, -1, "open(test_file, O_RDONLY | O_CREAT | O_EXCL)");
#if defined(__GLIBC__)
if (!TESTS_USE_IRT) {
// These final two cases rely on ensure_file_is_absent which does not work
// under glibc-non-irt.
return passed(testname, "all");
}
#endif
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.new_file", test_file);
// open new file with O_CREAT
ensure_file_is_absent(temp_file);
fd = open(temp_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
ASSERT_NE_MSG(fd, -1, "open(new_file, O_RDWR | O_CREAT)");
close(fd);
// open new file with O_EXCL
ensure_file_is_absent(temp_file);
fd = open(temp_file, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
ASSERT_NE_MSG(fd, -1, "open(new_file, O_RDWR | O_CREAT | O_EXCL)");
close(fd);
return passed(testname, "all");
}
bool test_stat(const char *test_file) {
struct stat buf;
// Test incoming test_file for read and write permission.
ASSERT_EQ(stat(test_file, &buf), 0);
ASSERT_MSG(buf.st_mode & S_IRUSR, "stat() failed to report S_IRUSR");
ASSERT_MSG(buf.st_mode & S_IWUSR, "stat() failed to report S_IWUSR");
// Test fstat and compare the result with the result of stat.
int fd = open(test_file, O_RDONLY);
ASSERT_NE(fd, -1);
struct stat buf2;
int rc = fstat(fd, &buf2);
ASSERT_EQ(rc, 0);
ASSERT_EQ(buf.st_dev, buf2.st_dev);
ASSERT_EQ(buf.st_mode, buf2.st_mode);
ASSERT_EQ(buf.st_nlink, buf2.st_nlink);
ASSERT_EQ(buf.st_uid, buf2.st_uid);
ASSERT_EQ(buf.st_gid, buf2.st_gid);
ASSERT_EQ(buf.st_rdev, buf2.st_rdev);
ASSERT_EQ(buf.st_size, buf2.st_size);
ASSERT_EQ(buf.st_blksize, buf2.st_blksize);
ASSERT_EQ(buf.st_blocks, buf2.st_blocks);
ASSERT_EQ(buf.st_ino, buf2.st_ino);
// Do not check st_atime as it seems to be updated by open or fstat
// on Windows.
// Disable the check on Windows for now because this check starts
// failing when the buildbots change timezone by entering or leaving
// Daylight Savings Time (DST).
// See https://bugs.chromium.org/p/nativeclient/issues/detail?id=3990.
if (!isWindows) {
ASSERT_EQ(buf.st_mtime, buf2.st_mtime);
ASSERT_EQ(buf.st_ctime, buf2.st_ctime);
}
rc = close(fd);
ASSERT_EQ(rc, 0);
// Open a new file to verify that st_dev matches and st_ino does not.
char temp_file[PATH_MAX];
snprintf(temp_file, PATH_MAX, "%s.tmp_stat", test_file);
fd = open(temp_file, O_CREAT, S_IRUSR | S_IWUSR);
ASSERT_NE(fd, -1);
rc = fstat(fd, &buf2);
ASSERT_EQ(rc, 0);
ASSERT_EQ(buf.st_dev, buf2.st_dev);
// Non-sfi-nacl mode always masks inode numbers so we should get the same
// value for all files.
if (NONSFI_MODE && !TESTS_USE_IRT) {
ASSERT_EQ(buf.st_ino, NACL_FAKE_INODE_NUM);
ASSERT_EQ(buf.st_ino, buf2.st_ino);
} else {
// Otherwise expect genuine (and therefore different) inode numbers.
// Except on Windows where we don't expose any inode numbers and
// they will both be zero.
if (buf.st_ino != NACL_FAKE_INODE_NUM &&
buf2.st_ino != NACL_FAKE_INODE_NUM) {
ASSERT_NE(buf.st_ino, buf2.st_ino);
}
}
rc = close(fd);
ASSERT_EQ(rc, 0);
// An invalid fstat call.
errno = 0;
ASSERT_EQ(fstat(-1, &buf2), -1);
ASSERT_EQ(errno, EBADF);
// Test a new read-only file
// The current unlink() implemenation in the sel_ldr for Windows
// doesn't support removing read-only files.
// TODO(sbc): enable this part of the test once this gets fixed.
#if 0
char buffer[PATH_MAX];
snprintf(buffer, PATH_MAX, "%s.readonly", test_file);
unlink(buffer);
ASSERT_EQ(stat(buffer, &buf), -1);
int fd = open(buffer, O_RDWR | O_CREAT, S_IRUSR);
ASSERT_NE(fd, -1);
ASSERT_EQ(close(fd), 0);
ASSERT_EQ(stat(buffer, &buf), 0);
ASSERT_MSG(buf.st_mode & S_IRUSR, "stat() failed to report S_IRUSR");
ASSERT_MSG(!(buf.st_mode & S_IWUSR), "S_IWUSR report for a read-only file");
#endif
// Windows doesn't support the concept of write only files,
// so we can't test this case.
return passed("test_stat", "all");
}
// close() returns 0 on success, -1 on error
bool test_close(const char *test_file) {
int fd;
int ret_val;
const char *testname = "test_close";
// file OK
fd = open(test_file, O_RDWR);
ASSERT_GE(fd, 0);
ret_val = close(fd);
ASSERT_EQ(ret_val, 0);
// file OK
fd = open(test_file, O_RDWR);
ASSERT_GE(fd, 0);
// close on wrong fd not OK
errno = 0;
ret_val = close(fd+1);
ASSERT_EQ(ret_val, -1);
// bad file number
ASSERT_EQ(errno, EBADF);
ret_val = close(fd);
ASSERT_EQ(ret_val, 0);
// file not OK
fd = open("file_none.txt", O_WRONLY);
ASSERT_EQ(fd, -1);
errno = 0;
ret_val = close(fd);
ASSERT_EQ(ret_val, -1);
// bad file number
ASSERT_EQ(errno, EBADF);
// directory OK
// Linux's open() (unsandboxed) does not allow O_RDWR on a directory.
// TODO(mseaborn): sel_ldr should reject O_RDWR on a directory too.
if (!NONSFI_MODE) {
fd = open(".", O_RDWR);
ASSERT_GE(fd, 0);
ret_val = close(fd);
ASSERT_EQ(ret_val, 0);
}
// directory not OK
fd = open("nosuchdir", O_RDWR);
ASSERT_EQ(fd, -1);
errno = 0;
ret_val = close(fd);
ASSERT_EQ(ret_val, -1);
// bad file number
ASSERT_EQ(errno, EBADF);
return passed(testname, "all");
}
// read() returns the number of bytes read on success (0 indicates end
// of file), -1 on error
bool test_read(const char *test_file) {
int fd;
int ret_val;
char out_char[5];
const char *testname = "test_read";
fd = open(test_file, O_RDONLY);
ASSERT_GE(fd, 0);
// fd OK, buffer OK, count OK
ret_val = read(fd, out_char, 1);
ASSERT_EQ(ret_val, 1);
errno = 0;
// fd not OK, buffer OK, count OK
ret_val = read(-1, out_char, 1);
ASSERT_EQ(ret_val, -1);
// bad file number
ASSERT_EQ(errno, EBADF);
errno = 0;
// fd OK, buffer OK, count not OK
// Linux's read() (unsandboxed) does not reject this buffer size.
if (!NONSFI_MODE) {
ret_val = read(fd, out_char, -1);
ASSERT_EQ(ret_val, -1);
// bad address
ASSERT_EQ(errno, EFAULT);
}
errno = 0;
// fd not OK, buffer OK, count not OK
ret_val = read(-1, out_char, -1);
ASSERT_EQ(ret_val, -1);
// bad descriptor
if (NONSFI_MODE) {
// Under qemu-arm, this read() call returns EFAULT.
if (EBADF != errno && EFAULT != errno)
return failed(testname, "errno is not EBADF or EFAULT");
} else {
ASSERT_EQ(errno, EBADF);
}
// fd OK, buffer OK, count 0
ret_val = read(fd, out_char, 0);
ASSERT_EQ(ret_val, 0);
// read 10, but only 3 are left
ret_val = read(fd, out_char, 10);
ASSERT_EQ(ret_val, 4);
// EOF
ret_val = read(fd, out_char, 10);
ASSERT_EQ(ret_val, 0);
close(fd);
return passed(testname, "all");
}
// write() returns the number of bytes written on success, -1 on error
bool test_write(const char *test_file) {
int fd;
int ret_val;
char out_char[] = "12";
const char *testname = "test_write";
fd = open(test_file, O_WRONLY);
ASSERT_GE(fd, 0);
// all params OK
ret_val = write(fd, out_char, 2);
ASSERT_EQ(ret_val, 2);
errno = 0;
// invalid count
// Linux's write() (unsandboxed) does not reject this buffer size.
if (!NONSFI_MODE) {
ret_val = write(fd, out_char, -1);
ASSERT_EQ(ret_val, -1);
// bad address
ASSERT_EQ(errno, EFAULT);
}
errno = 0;
// invalid fd
ret_val = write(-1, out_char, 2);
ASSERT_EQ(ret_val, -1);
// bad address
ASSERT_EQ(errno, EBADF);
close(fd);
return passed(testname, "all");
}
// lseek returns the resulting offset location in bytes from the
// beginning of the file, -1 on error
bool test_lseek(const char *test_file) {
int fd;
int ret_val;
char out_char;
const char *testname = "test_lseek";
fd = open(test_file, O_RDWR);
ASSERT_GE(fd, 0);
ret_val = lseek(fd, 2, SEEK_SET);
ASSERT_EQ(ret_val, 2);
errno = 0;
ret_val = lseek(-1, 1, SEEK_SET);
ASSERT_EQ(ret_val, -1);
// bad file number
ASSERT_EQ(errno, EBADF);
ret_val = read(fd, &out_char, 1);
ASSERT_EQ(ret_val, 1);
ASSERT_EQ(out_char, '3');
ret_val = lseek(fd, 1, SEEK_CUR);
ASSERT_EQ(ret_val, 4);
ret_val = read(fd, &out_char, 1);
ASSERT_EQ(ret_val, 1);
ASSERT_EQ(out_char, '5');
ret_val = lseek(fd, -1, SEEK_CUR);
ASSERT_EQ(ret_val, 4);
ret_val = read(fd, &out_char, 1);
ASSERT_EQ(ret_val, 1);
ASSERT_EQ(out_char, '5');
ret_val = lseek(fd, -2, SEEK_END);
ASSERT_EQ(ret_val, 3);
ret_val = read(fd, &out_char, 1);
ASSERT_EQ(ret_val, 1);
ASSERT_EQ(out_char, '4');
ret_val = lseek(fd, 4, SEEK_END);
// lseek allows for positioning beyond the EOF
ASSERT_EQ(ret_val, 9);
ret_val = lseek(fd, 4, SEEK_SET);
ASSERT_EQ(ret_val, 4);
errno = 0;
ret_val = lseek(fd, 4, SEEK_END + 3);
ASSERT_EQ(ret_val, -1);
// invalid argument
ASSERT_EQ(errno, EINVAL);
errno = 0;
ret_val = lseek(fd, -40, SEEK_SET);
ASSERT_EQ(ret_val, -1);
// invalid argument
ASSERT_EQ(errno, EINVAL);
ret_val = read(fd, &out_char, 1);
ASSERT_EQ(ret_val, 1);
ASSERT_EQ(out_char, '5');
close(fd);
return passed(testname, "all");
}
// Verify that opendir of the root directory works. There was a win32 bug
// that caused this specific case to fail:
// https://code.google.com/p/nativeclient/issues/detail?id=4328
bool test_opendir_root() {
// TODO(mseaborn): Implement listing directories for unsandboxed mode.
if (NONSFI_MODE)
return true;
DIR *d = opendir("/");
ASSERT_NE_MSG(d, NULL, "opendir('/') failed");
ASSERT_EQ(0, closedir(d));
d = opendir("");
ASSERT_EQ_MSG(d, NULL, "opendir('') failed to fail");
return true;
}
bool test_readdir(const char *test_file) {
// TODO(mseaborn): Implement listing directories for unsandboxed mode.
if (NONSFI_MODE)
return true;
// Read the directory containing the test file
// Split filename into basename and dirname.
char dirname[PATH_MAX];
strncpy(dirname, test_file, PATH_MAX);
char *basename = split_name(dirname);
// Read the directory listing and verify that the test_file is
// present.
int found = 0;
DIR *d = opendir(dirname);
ASSERT_NE_MSG(d, NULL, "opendir failed");
int count = 0;
struct dirent *ent;
while (1) {
ent = readdir(d);
if (!ent)
break;
if (!strcmp(ent->d_name, basename))
found = 1;
count++;
}
ASSERT_EQ_MSG(1, found, "failed to find test file in directory listing");
// Rewind directory and verify that the number of elements
// matches the previous count.
rewinddir(d);
while (readdir(d))
count--;
ASSERT_EQ_MSG(0, count, "readdir after rewinddir was inconsistent");
ASSERT_EQ(0, closedir(d));
return passed("test_readdir", "all");
}
// isatty returns 1 for TTY descriptors and 0 on error (setting errno)
bool test_isatty(const char *test_file) {
// TODO(mseaborn): Implement isatty() for unsandboxed mode.
// We need to write two if-statements two avoid clang's warning.
if (NONSFI_MODE)
if (TESTS_USE_IRT)
return true;
// TODO(sbc): isatty() in glibc is not yet hooked up to the IRT
// interfaces. Remove this conditional once this gets addressed:
// https://code.google.com/p/nativeclient/issues/detail?id=3709
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 9
return true;
#endif
// Open a regular file that check that it is not a tty.
int fd = open(test_file, O_RDONLY);
ASSERT_NE_MSG(fd, -1, "open() failed");
errno = 0;
ASSERT_EQ_MSG(isatty(fd), 0, "isatty returned non-zero");
ASSERT_EQ_MSG(errno, ENOTTY, "isatty failed to set errno to ENOTTY");
close(fd);
// Verify that isatty() on closed file returns 0 and sets errno to EBADF
errno = 0;
ASSERT_EQ_MSG(isatty(fd), 0, "isatty returned non-zero");
ASSERT_EQ_MSG(errno, EBADF, "isatty failed to set errno to EBADF");
// On Linux opening /dev/ptmx always returns a TTY file descriptor.
fd = open("/dev/ptmx", O_RDWR);
if (fd >= 0) {
errno = 0;
ASSERT_EQ(isatty(fd), 1);
ASSERT_EQ(errno, 0);
close(fd);
}
return passed("test_isatty", "all");
}
/*
* Not strictly speaking a syscall, but we have a 'fake' implementation
* that we want to test.
*/
bool test_gethostname() {
char hostname[256];
ASSERT_EQ(gethostname(hostname, 1), -1);
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 9
// glibc only provides a stub gethostbyname() that returns
// ENOSYS in all cases.
ASSERT_EQ(errno, ENOSYS);
#else
ASSERT_EQ(errno, ENAMETOOLONG);
errno = 0;
ASSERT_EQ(gethostname(hostname, 256), 0);
ASSERT_EQ(errno, 0);
ASSERT_EQ(strcmp(hostname, "naclhost"), 0);
#endif
return passed("test_gethostname", "all");
}
/*
* function testSuite()
*
* Run through a complete sequence of file tests.
*
* returns true if all tests succeed. false if one or more fail.
*/
bool testSuite(const char *test_file) {
bool ret = true;
// The order of executing these tests matters!
ret &= test_sched_yield();
ret &= test_sysconf();
ret &= test_stat(test_file);
ret &= test_open(test_file);
ret &= test_open_trunc(test_file);
ret &= test_open_directory(test_file);
ret &= test_close(test_file);
ret &= test_read(test_file);
ret &= test_write(test_file);
ret &= test_lseek(test_file);
ret &= test_opendir_root();
ret &= test_readdir(test_file);
ret &= test_gethostname();
// glibc support for calling syscalls directly, without the IRT, is limited
// so we skip certain tests in this case.
#if !defined(__GLIBC__) || TESTS_USE_IRT
ret &= test_unlink(test_file);
ret &= test_chdir();
ret &= test_fchdir();
ret &= test_mkdir_rmdir(test_file);
ret &= test_getcwd();
ret &= test_mkdir_rmdir(test_file);
ret &= test_isatty(test_file);
ret &= test_rename(test_file);
ret &= test_link(test_file);
ret &= test_symlinks(test_file);
ret &= test_chmod(test_file);
ret &= test_fchmod(test_file);
ret &= test_access(test_file);
ret &= test_fsync(test_file);
ret &= test_fdatasync(test_file);
ret &= test_utimes(test_file);
#endif
// TODO(sbc): remove this restriction once glibc's truncate calls
// is hooked up to the IRT dev-filename-0.2 interface:
// https://code.google.com/p/nativeclient/issues/detail?id=3709
#if !(defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 9)
ret &= test_truncate(test_file);
ret &= test_ftruncate(test_file);
#endif
return ret;
}
/*
* main entry point.
*
* run all tests and call system exit with appropriate value
* 0 - success, all tests passed.
* -1 - one or more tests failed.
*/
int main(const int argc, const char *argv[]) {
bool passed;
if (argc != 3) {
printf("Please specify the test file name\n");
exit(-1);
}
if (argv[2][0] == 'w') {
isWindows = true;
}
// run the full test suite
passed = testSuite(argv[1]);
if (passed) {
printf("All tests PASSED\n");
exit(0);
} else {
printf("One or more tests FAILED\n");
exit(-1);
}
}