Add the tester
This commit is contained in:
parent
042e4e8714
commit
a625bbff50
@ -4,15 +4,14 @@ require 'net/http'
|
|||||||
require 'rake/clean'
|
require 'rake/clean'
|
||||||
require 'open3'
|
require 'open3'
|
||||||
require 'etc'
|
require 'etc'
|
||||||
|
require_relative 'shared'
|
||||||
|
|
||||||
GCC_VERSION = "14.2.0"
|
GCC_VERSION = "14.2.0"
|
||||||
BINUTILS_VERSION = '2.43.1'
|
BINUTILS_VERSION = '2.43.1'
|
||||||
GLIBC_VERSION = '2.40'
|
GLIBC_VERSION = '2.40'
|
||||||
KERNEL_VERSION = '5.15.166'
|
KERNEL_VERSION = '5.15.166'
|
||||||
|
|
||||||
TMP = Pathname.new('./build')
|
CLOBBER.include TMP
|
||||||
|
|
||||||
CLEAN.include TMP
|
|
||||||
|
|
||||||
class BuildTarget
|
class BuildTarget
|
||||||
attr_accessor(:build, :gcc, :target, :tmp)
|
attr_accessor(:build, :gcc, :target, :tmp)
|
||||||
@ -87,11 +86,16 @@ def download_and_unarchive(url, target)
|
|||||||
download_and_unarchive URI.parse(response['location'])
|
download_and_unarchive URI.parse(response['location'])
|
||||||
when Net::HTTPSuccess
|
when Net::HTTPSuccess
|
||||||
Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread|
|
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|
|
response.read_body do |chunk|
|
||||||
stdin.write chunk
|
stdin.write chunk
|
||||||
end
|
end
|
||||||
|
stdin.close
|
||||||
|
|
||||||
|
wait_thread.value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
response.error!
|
response.error!
|
||||||
@ -301,7 +305,27 @@ namespace :cross do
|
|||||||
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
|
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
|
||||||
sh env, 'make', 'install', chdir: cwd.to_path
|
sh env, 'make', 'install', chdir: cwd.to_path
|
||||||
end
|
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
|
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
|
end
|
||||||
|
1
rakelib/shared.rb
Normal file
1
rakelib/shared.rb
Normal file
@ -0,0 +1 @@
|
|||||||
|
TMP = Pathname.new('./build')
|
80
rakelib/tester.rake
Normal file
80
rakelib/tester.rake
Normal file
@ -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
|
0
tests/expectations/empty.txt
Normal file
0
tests/expectations/empty.txt
Normal file
0
tests/vm/empty.elna
Normal file
0
tests/vm/empty.elna
Normal file
205
tools/init.c
Normal file
205
tools/init.c
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user