add a test suite framework

It's hard to make changes w/out tests as many of these funcs will only
trigger errors when they actually get used. Start a simple test suite
so changes can quickly be tested & debugged.

Bug: chromium:219015
Test: compiled the code for x86_64 (64bit) & x86_64 (x32) & ran tests
Change-Id: Ic3107a35285c1e9288a9d9249790330e54501115
Reviewed-on: https://chromium-review.googlesource.com/446421
Reviewed-by: Mark Seaborn <mseaborn@chromium.org>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4032eb0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+
+core
diff --git a/README.md b/README.md
index 12224da..810806e 100644
--- a/README.md
+++ b/README.md
@@ -114,14 +114,17 @@
 
 ### Testing
 
-Unfortunately, LSS has no automated test suite.
+Tests are found in the [tests/](./tests/) subdirectory.  It does not (yet) offer
+100% coverage, but should grow over time.
 
-You can test LSS by patching it into Chromium, building Chromium, and running
-Chromium's tests.
+New commits that update/change/add syscall wrappers should include tests for
+them too.  Consult the [test documentation](./tests/README.md) for more details.
 
-You can compile-test LSS by running:
+To run, just run `make` inside the tests directory.  It will compile & execute
+the tests locally.
 
-    gcc -Wall -Wextra -Wstrict-prototypes -c linux_syscall_support.h
+There is some limited cross-compile coverage available if you run `make cross`.
+It only compiles things (does not execute at all).
 
 ### Rolling into Chromium
 
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..89c5d3c
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,4 @@
+/*_test
+
+# Some tests create temp files.
+/tempfile.*
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..d91dedc
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,129 @@
+# Copyright 2018, Google Inc.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+top_srcdir ?= ..
+
+DEF_FLAGS = -g -pipe
+DEF_WFLAGS = -Wall
+CFLAGS ?= $(DEF_FLAGS)
+CXXFLAGS ?= $(DEF_FLAGS)
+CFLAGS += $(DEF_WFLAGS) -Wstrict-prototypes
+CXXFLAGS += $(DEF_WFLAGS)
+CPPFLAGS += -I$(top_srcdir)
+# We use static linking here so that if people run through qemu/etc... by hand,
+# it's a lot easier to run/debug.  Same for strace output.
+LDFLAGS += -static
+
+TESTS = \
+	unlink \
+
+all: check
+
+%_test: %.c test_skel.h $(top_srcdir)/linux_syscall_support.h
+	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<
+
+%_test: %.cc test_skel.h $(top_srcdir)/linux_syscall_support.h
+	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<
+
+%_run: %_test
+	@t=$(@:_run=_test); \
+	echo "./$$t"; \
+	if ! env -i ./$$t; then \
+		env -i strace -f -v ./$$t; \
+		echo "TRY: gdb -q -ex r -ex bt ./$$t"; \
+		exit 1; \
+	fi
+
+ALL_TEST_TARGETS = $(TESTS:=_test)
+compile_tests: $(ALL_TEST_TARGETS)
+
+ALL_RUN_TARGETS = $(TESTS:=_run)
+check: $(ALL_RUN_TARGETS)
+
+# The "tempfile" targets are the names we use with temp files.
+# Clean them out in case some tests crashed in the middle.
+clean:
+	rm -f *~ *.o tempfile.* a.out core $(ALL_TEST_TARGETS)
+
+.SUFFIXES:
+.PHONY: all check clean compile_tests
+.SECONDARY: $(ALL_TEST_TARGETS)
+
+# Try to cross-compile the tests for all our supported arches.  We test with
+# both gcc and clang.  We don't support execution (yet?), but just compiling
+# & linking helps catch common bugs.
+.PHONY: cross compile_cross
+cross_compile:
+	@echo "Running: $(MAKE) $@ CC='$(CC)' CXX='$(CXX)'"; \
+	if (echo '#include <stdio.h>' | $(CC) -x c -c -o /dev/null -) 2>/dev/null; then \
+		$(MAKE) -s clean; \
+		$(MAKE) -k --no-print-directory compile_tests; \
+	else \
+		echo "Skipping $(CC) test: not installed"; \
+	fi; \
+	echo
+
+# The names here are a best effort.  Not easy to probe for.
+cross:
+	@for cc in \
+		"x86_64-pc-linux-gnu-gcc" \
+		"i686-pc-linux-gnu-gcc" \
+		"x86_64-pc-linux-gnu-gcc -mx32" \
+		"armv7a-unknown-linux-gnueabi-gcc -marm -mhard-float" \
+		"armv7a-unknown-linux-gnueabi-gcc -mthumb -mhard-float" \
+		"powerpc-unknown-linux-gnu-gcc" \
+		"aarch64-unknown-linux-gnu-gcc" \
+		"mips64-unknown-linux-gnu-gcc -mabi=64" \
+		"mips64-unknown-linux-gnu-gcc -mabi=32" \
+		"mips64-unknown-linux-gnu-gcc -mabi=n32" \
+		"s390-ibm-linux-gnu-gcc" \
+		"s390x-ibm-linux-gnu-gcc" \
+	; do \
+		cxx=`echo "$$cc" | sed 's:-gcc:-g++:'`; \
+		$(MAKE) --no-print-directory CC="$$cc" CXX="$$cxx" cross_compile; \
+		\
+		sysroot=`$$cc --print-sysroot 2>/dev/null`; \
+		gccdir=`$$cc -print-file-name=libgcc.a 2>/dev/null`; \
+		gccdir=`dirname "$$gccdir"`; \
+		: Skip building for clang for mips/o32 and s390/31-bit until it works.; \
+		case $$cc in \
+			mips64*-mabi=32) continue;; \
+			s390-*) continue;; \
+		esac; \
+		set -- $$cc; \
+		tuple=$${1%-gcc}; \
+		shift; \
+		cc="clang -target $$tuple $$*"; \
+		: Assume the build system is x86_64 based, so ignore the sysroot.; \
+		case $$tuple in \
+			x86_64*) ;; \
+			*) cc="$$cc --sysroot $$sysroot -B$$gccdir -L$$gccdir";; \
+		esac; \
+		cxx=`echo "$$cc" | sed 's:^clang:clang++:'`; \
+		$(MAKE) --no-print-directory CC="$$cc" CXX="$$cxx" cross_compile; \
+	done
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..45af3c3
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,52 @@
+# LSS Tests
+
+## Source Layout
+
+The general layout of the tests:
+* [test_skel.h]: Test helpers for common checks/etc...
+* xxx.c: Unittest for the xxx syscall (e.g. `open.c`).
+* [Makefile]: New tests should be registered in the `TESTS` variable.
+
+## Test Guidelines
+
+The unittest itself generally follows the conventions:
+* Written in C (unless a very specific language behavior is needed).
+* You should only need to `#include "test_skel.h"`.  For new system headers, try
+  to add them here rather than copying to exact unittest (if possible).
+  It might slow compilation down slightly, but makes the code easier to manage.
+  Make sure it is included first.
+* Use `assert()` on everything to check return values.
+* Use `sys_xxx()` to access the syscall via LSS (compared to `xxx()` which tends
+  to come from the C library).
+* If you need a tempfile, use `tempfile.XXXXXX` for templates with helpers like
+  `mkstemp`.  Try to clean them up when you're done with them.
+  These will be created in the cwd, but that's fine.
+* Don't worry about trying to verify the kernel/C library API and various edge
+  cases.  The goal of LSS is to make sure that we pass args along correctly to
+  the syscall only.
+* Make sure to leave comments in the test so it's clear what behavior you're
+  trying to verify (and how).
+
+Feel free to extend [test_skel.h] with more helpers if they're useful to more
+than one test.
+
+If you're looking for a simple example, start with [unlink.c](./unlink.c).
+You should be able to copy this over and replace the content of `main()`.
+
+## Running The Tests
+
+Simply run `make`.  This will compile & execute all the tests on your local
+system.  A standard `make clean` will clean up all the objects.
+
+If you need to debug something, then the programs are simply named `xxx_test`
+and can easily be thrown into `gdb ./xxx_test`.
+
+We have rudimentary cross-compile testing via gcc and clang.  Try running
+`make cross` -- for any toolchains you don't have available, it should skip
+things automatically.  This only verifies the compilation & linking stages
+though.
+
+The cross-compilers can be created using <http://crosstool-ng.github.io/>.
+
+[Makefile]: ./Makefile
+[test_skel.h]: ./test_skel.h
diff --git a/tests/test_skel.h b/tests/test_skel.h
new file mode 100644
index 0000000..9ff0eb3
--- /dev/null
+++ b/tests/test_skel.h
@@ -0,0 +1,70 @@
+/* Copyright 2018, Google Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Make sure it's defined before including anything else.  A number of syscalls
+ * are GNU extensions and rely on being exported by glibc.
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+/*
+ * Make sure the assert checks aren't removed as all the unittests are based
+ * on them.
+ */
+#undef NDEBUG
+
+#include <assert.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/wait.h>
+
+#include <linux/capability.h>
+
+#include "linux_syscall_support.h"
+
+void assert_buffers_eq_len(const void *buf1, const void *buf2, size_t len) {
+  const uint8_t *u8_1 = (const uint8_t *)buf1;
+  const uint8_t *u8_2 = (const uint8_t *)buf2;
+  size_t i;
+
+  for (i = 0; i < len; ++i) {
+    if (u8_1[i] != u8_2[i])
+      printf("offset %zu: %02x != %02x\n", i, u8_1[i], u8_2[i]);
+  }
+}
+#define assert_buffers_eq(obj1, obj2) assert_buffers_eq_len(obj1, obj2, sizeof(*obj1))
diff --git a/tests/unlink.c b/tests/unlink.c
new file mode 100644
index 0000000..70c8bc9
--- /dev/null
+++ b/tests/unlink.c
@@ -0,0 +1,48 @@
+/* Copyright 2018, Google Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test_skel.h"
+
+int main(int argc, char *argv[]) {
+  // Get a unique path to play with.
+  char foo[] = "tempfile.XXXXXX";
+  int fd = mkstemp(foo);
+  assert(fd != -1);
+
+  // Make sure it exists.
+  assert(access(foo, F_OK) == 0);
+
+  // Then delete it.
+  assert(sys_unlink(foo) == 0);
+
+  // Make sure it's gone.
+  assert(access(foo, F_OK) != 0);
+
+  return 0;
+}