summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rakelib/cross.rake34
-rw-r--r--rakelib/shared.rb1
-rw-r--r--rakelib/tester.rake80
-rw-r--r--tests/expectations/empty.txt0
-rw-r--r--tests/vm/empty.elna0
-rw-r--r--tools/init.c205
6 files changed, 315 insertions, 5 deletions
diff --git a/rakelib/cross.rake b/rakelib/cross.rake
index 3de7d79..700a8dd 100644
--- a/rakelib/cross.rake
+++ b/rakelib/cross.rake
@@ -4,15 +4,14 @@ require 'net/http'
require 'rake/clean'
require 'open3'
require 'etc'
+require_relative 'shared'
GCC_VERSION = "14.2.0"
BINUTILS_VERSION = '2.43.1'
GLIBC_VERSION = '2.40'
KERNEL_VERSION = '5.15.166'
-TMP = Pathname.new('./build')
-
-CLEAN.include TMP
+CLOBBER.include TMP
class BuildTarget
attr_accessor(:build, :gcc, :target, :tmp)
@@ -87,11 +86,16 @@ def download_and_unarchive(url, target)
download_and_unarchive URI.parse(response['location'])
when Net::HTTPSuccess
Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread|
- stdout.close
+ Thread.new do
+ stdout.each { |line| puts line }
+ end
response.read_body do |chunk|
stdin.write chunk
end
+ stdin.close
+
+ wait_thread.value
end
else
response.error!
@@ -301,7 +305,27 @@ namespace :cross do
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', 'install', chdir: cwd.to_path
end
+
+ task :init, [:target] do |_, args|
+ options = find_build_target GCC_VERSION, args
+ env = {
+ 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}"
+ }
+ sh env, 'riscv32-unknown-linux-gnu-gcc',
+ '-ffreestanding', '-static',
+ '-o', (options.tools + 'init').to_path,
+ 'tools/init.c'
+ end
end
-task cross: ['cross:binutils', 'cross:gcc1', 'cross:headers', 'cross:kernel', 'cross:glibc', 'cross:gcc2'] do
+desc 'Build cross toolchain'
+task cross: [
+ 'cross:binutils',
+ 'cross:gcc1',
+ 'cross:headers',
+ 'cross:kernel',
+ 'cross:glibc',
+ 'cross:gcc2',
+ 'cross:init'
+] do
end
diff --git a/rakelib/shared.rb b/rakelib/shared.rb
new file mode 100644
index 0000000..8ecd8cb
--- /dev/null
+++ b/rakelib/shared.rb
@@ -0,0 +1 @@
+TMP = Pathname.new('./build')
diff --git a/rakelib/tester.rake b/rakelib/tester.rake
new file mode 100644
index 0000000..ef05556
--- /dev/null
+++ b/rakelib/tester.rake
@@ -0,0 +1,80 @@
+require 'open3'
+require 'rake/clean'
+require_relative 'shared'
+
+CLEAN.include(TMP + 'riscv')
+
+LINKER = 'build/rootfs/riscv32-unknown-linux-gnu/bin/ld'
+
+namespace :test do
+ test_sources = FileList['tests/vm/*.elna']
+ compiler = `cabal list-bin elna`.strip
+ object_directory = TMP + 'riscv/tests'
+ root_directory = TMP + 'riscv/root'
+ executable_directory = root_directory + 'tests'
+ expectation_directory = root_directory + 'expectations'
+ init = TMP + 'riscv/root/init'
+
+ directory root_directory
+ directory object_directory
+ directory executable_directory
+ directory expectation_directory
+
+ test_files = test_sources.flat_map do |test_source|
+ test_basename = File.basename(test_source, '.elna')
+ test_object = object_directory + test_basename.ext('.o')
+
+ file test_object => [test_source, object_directory] do
+ sh compiler, '--output', test_object.to_path, test_source
+ end
+ test_executable = executable_directory + test_basename
+
+ file test_executable => [test_object, executable_directory] do
+ sh LINKER, '-o', test_executable.to_path, test_object.to_path
+ end
+ expectation_name = test_basename.ext '.txt'
+ source_expectation = "tests/expectations/#{expectation_name}"
+ target_expectation = expectation_directory + expectation_name
+
+ file target_expectation => [source_expectation, expectation_directory] do
+ cp source_expectation, target_expectation
+ end
+
+ [test_executable, target_expectation]
+ end
+
+ file init => [root_directory] do |task|
+ cp (TMP + 'tools/init'), task.name
+ end
+ test_files << init << executable_directory << expectation_directory
+
+ file (TMP + 'riscv/root.cpio') => test_files do |task|
+ root_files = task.prerequisites
+ .map { |prerequisite| Pathname.new(prerequisite).relative_path_from(root_directory).to_path }
+
+ File.open task.name, 'wb' do |cpio_file|
+ cpio_options = {
+ chdir: root_directory.to_path
+ }
+ cpio_stream = Open3.popen2 'cpio', '-o', '--format=newc', cpio_options do |stdin, stdout, wait_thread|
+ stdin.write root_files.join("\n")
+ stdin.close
+ stdout.each { |chunk| cpio_file.write chunk }
+ wait_thread.value
+ end
+ end
+ end
+
+ task :vm => (TMP + 'riscv/root.cpio') do |task|
+ kernels = FileList.glob(TMP + 'tools/linux-*/arch/riscv/boot/Image')
+
+ sh 'qemu-system-riscv32',
+ '-nographic',
+ '-M', 'virt',
+ '-bios', 'default',
+ '-kernel', kernels.first,
+ '-append', 'quiet panic=1',
+ '-initrd', task.prerequisites.first,
+ '-no-reboot'
+ end
+end
diff --git a/tests/expectations/empty.txt b/tests/expectations/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/expectations/empty.txt
diff --git a/tests/vm/empty.elna b/tests/vm/empty.elna
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/vm/empty.elna
diff --git a/tools/init.c b/tools/init.c
new file mode 100644
index 0000000..cb646bd
--- /dev/null
+++ b/tools/init.c
@@ -0,0 +1,205 @@
+#include <stdio.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/reboot.h>
+
+#define FILENAME_BUFFER_SIZE 256
+
+size_t read_command(int descriptor, char *command_buffer)
+{
+ ssize_t bytes_read = 0;
+ size_t read_so_far = 0;
+
+ while ((bytes_read = read(descriptor, command_buffer + read_so_far, FILENAME_BUFFER_SIZE - read_so_far - 1)) > 0)
+ {
+ read_so_far += bytes_read;
+ if (read_so_far >= FILENAME_BUFFER_SIZE - 1)
+ {
+ break;
+ }
+ }
+ command_buffer[read_so_far] = 0;
+ return read_so_far;
+}
+
+enum status
+{
+ status_success,
+ status_failure,
+ status_warning,
+ status_fatal
+};
+
+unsigned int make_path(char *destination, const char *directory, const char *filename, const char *extension)
+{
+ unsigned int i = 0;
+
+ for (; i < FILENAME_BUFFER_SIZE; i++)
+ {
+ if (directory[i] == 0)
+ {
+ break;
+ }
+ destination[i] = directory[i];
+ }
+ for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
+ {
+ if (filename[j] == 0)
+ {
+ break;
+ }
+ destination[i] = filename[j];
+ }
+ if (extension == NULL)
+ {
+ goto done;
+ }
+ for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
+ {
+ if (extension[j] == 0)
+ {
+ break;
+ }
+ destination[i] = extension[j];
+ }
+done:
+ destination[i] = 0;
+
+ return i;
+}
+
+enum status run_test(const char *file_entry_name)
+{
+ printf("Running %s. ", file_entry_name);
+
+ char filename[FILENAME_BUFFER_SIZE];
+ char command_buffer[FILENAME_BUFFER_SIZE];
+ char file_buffer[256];
+ int pipe_ends[2];
+
+ if (pipe(pipe_ends) == -1)
+ {
+ perror("pipe");
+ return status_fatal;
+ }
+ make_path(filename, "./tests/", file_entry_name, NULL);
+
+ int child_pid = fork();
+ if (child_pid == -1)
+ {
+ return status_fatal;
+ }
+ else if (child_pid == 0)
+ {
+ close(STDIN_FILENO);
+ close(STDERR_FILENO);
+ close(pipe_ends[0]); // Close the read end.
+
+ if (dup2(pipe_ends[1], STDOUT_FILENO) == -1)
+ {
+ perror("dup2");
+ }
+ else
+ {
+ execl(filename, filename);
+ perror("execl");
+ }
+ close(STDOUT_FILENO);
+ close(pipe_ends[1]);
+ _exit(1);
+ }
+ else
+ {
+ close(pipe_ends[1]); // Close the write end.
+ read_command(pipe_ends[0], command_buffer);
+ close(pipe_ends[0]);
+
+ int wait_status = 0;
+ wait(&wait_status);
+
+ make_path(filename, "./expectations/", file_entry_name, ".txt");
+
+ FILE *expectation_descriptor = fopen(filename, "r");
+
+ if (expectation_descriptor == NULL)
+ {
+ return status_warning;
+ }
+ size_t read_from_file = fread(file_buffer, 1, sizeof(file_buffer) - 1, expectation_descriptor);
+ fclose(expectation_descriptor);
+
+ file_buffer[read_from_file] = 0;
+ for (unsigned int i = 0; ; ++i)
+ {
+ if (command_buffer[i] == 0 && file_buffer[i] == 0)
+ {
+ fwrite("\n", 1, 1, stdout);
+ return status_success;
+ }
+ else if (command_buffer[i] != file_buffer[i])
+ {
+ printf("Failed. Got:\n%s", command_buffer);
+ return status_failure;
+ }
+ }
+ }
+}
+
+struct summary
+{
+ size_t total;
+ size_t failure;
+ size_t success;
+};
+
+void walk()
+{
+ DIR *directory_stream = opendir("./tests");
+ struct dirent *file_entry;
+
+ struct summary test_summary = { .total = 0, .failure = 0, .success = 0 };
+
+ while ((file_entry = readdir(directory_stream)) != NULL)
+ {
+ if (file_entry->d_name[0] == '.')
+ {
+ continue;
+ }
+ ++test_summary.total;
+ switch (run_test(file_entry->d_name))
+ {
+ case status_failure:
+ ++test_summary.failure;
+ break;
+ case status_success:
+ ++test_summary.success;
+ break;
+ case status_warning:
+ break;
+ case status_fatal:
+ goto end_walk;
+ }
+ }
+ printf("Successful: %lu, Failed: %lu, Total: %lu.\n",
+ test_summary.success, test_summary.failure, test_summary.total);
+end_walk:
+ closedir(directory_stream);
+}
+
+int main()
+{
+ int dev_console = open("/dev/console", O_WRONLY);
+ if (dev_console != -1)
+ {
+ dup2(dev_console, STDOUT_FILENO);
+ walk();
+ close(dev_console);
+ }
+ sync();
+ reboot(RB_POWER_OFF);
+
+ return 1;
+}