Déan comparáid idir tiomáintí
	
		
			12 Tiomáintí
		
	
	
		
			278ae31e2e
			...
			WIP/cpp
		
	
	| Údar | SHA1 | Dáta | |
|---|---|---|---|
| 73dae3a102 | |||
| f2ca26010a | |||
| 0f39ee7061 | |||
| 41bd6e8f10 | |||
| 7f083909e6 | |||
| 732623fc6f | |||
| dcfbe641f9 | |||
| 72a3b041d9 | |||
| c57ba807d7 | |||
| 6f19c53acf | |||
| 99d26480d7 | |||
| f13cff754c | 
							
								
								
									
										20
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,17 +1,27 @@
 | 
			
		||||
prefix = /usr/local
 | 
			
		||||
bindir = $(prefix)/bin
 | 
			
		||||
CFLAGS = -std=c99 -Wall -Wextra -pedantic
 | 
			
		||||
CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic
 | 
			
		||||
 | 
			
		||||
.DEFAULT_GOAL := test
 | 
			
		||||
.DEFAULT_GOAL := tests
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	rm -f test
 | 
			
		||||
	rm -f test exile.o testcpp
 | 
			
		||||
 | 
			
		||||
test: test.c
 | 
			
		||||
	$(CC) test.c -g $(CFLAGS) -o test
 | 
			
		||||
 | 
			
		||||
check: test
 | 
			
		||||
exile.o: exile.c exile.h
 | 
			
		||||
	$(CC) -c exile.c -g $(CFLAGS) -o exile.o
 | 
			
		||||
 | 
			
		||||
test: test.c exile.h exile.o
 | 
			
		||||
	$(CC) test.c exile.o -g $(CFLAGS) -o test
 | 
			
		||||
 | 
			
		||||
testcpp: test.cpp exile.h exile.hpp exile.o
 | 
			
		||||
	$(CXX) test.cpp exile.o -g $(CXXFLAGS) -o testcpp
 | 
			
		||||
 | 
			
		||||
tests: test testcpp
 | 
			
		||||
 | 
			
		||||
check: tests
 | 
			
		||||
	./test.sh
 | 
			
		||||
 | 
			
		||||
.PHONY: check
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1854
									
								
								exile.c
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										1854
									
								
								exile.c
									
									
									
									
									
										Comhad gnáth
									
								
							
										
											
												Tá difríocht comhad cosc orthu toisc go bhfuil sé ró-mhór
												Difríocht Luchtaigh
											
										
									
								
							
							
								
								
									
										1757
									
								
								exile.h
									
									
									
									
									
								
							
							
						
						
									
										1757
									
								
								exile.h
									
									
									
									
									
								
							
										
											
												Tá difríocht comhad cosc orthu toisc go bhfuil sé ró-mhór
												Difríocht Luchtaigh
											
										
									
								
							
							
								
								
									
										199
									
								
								exile.hpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										199
									
								
								exile.hpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
#include "exile.h"
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <sys/wait.h>
 | 
			
		||||
 | 
			
		||||
#ifndef EXILE_MMAP_SIZE
 | 
			
		||||
#define EXILE_MMAP_SIZE 128 * 1024 * 1024 //128MB
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
class launch_arg
 | 
			
		||||
{
 | 
			
		||||
	static_assert(std::is_trivially_copyable_v<T>);
 | 
			
		||||
	static_assert(!std::is_pointer_v<T>);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	struct exile_policy *policy;
 | 
			
		||||
	T *result_shm;
 | 
			
		||||
	U fn;
 | 
			
		||||
	std::tuple<Args...> args;
 | 
			
		||||
 | 
			
		||||
	launch_arg(struct exile_policy *policy, T *result_shm, U fn, Args && ... args) : policy(policy),
 | 
			
		||||
	result_shm(result_shm), fn(fn), args(std::forward<Args>(args)...) {}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
class launch_arg_serializer
 | 
			
		||||
{
 | 
			
		||||
	static_assert(std::is_copy_constructible_v<T>);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	struct exile_policy *policy;
 | 
			
		||||
	char *serialize_buffer;
 | 
			
		||||
	size_t n;
 | 
			
		||||
	U fn;
 | 
			
		||||
	std::tuple<Args...> args;
 | 
			
		||||
 | 
			
		||||
	const std::function<size_t (const T &, char *, size_t n)> &serializer;
 | 
			
		||||
	const std::function<T(const char * buf, size_t n)> &deserializer;
 | 
			
		||||
 | 
			
		||||
	launch_arg_serializer(struct exile_policy *policy, char *serialize_buffer, size_t n, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args) : policy(policy), serialize_buffer(serialize_buffer), n(n), fn(fn), args(std::forward<Args>(args)...), serializer(serializer), deserializer(deserializer)  {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
int exile_clone_handle_trivial(void * arg)
 | 
			
		||||
{
 | 
			
		||||
	static_assert(std::is_trivially_copyable_v<T>);
 | 
			
		||||
	static_assert(!std::is_pointer_v<T>);
 | 
			
		||||
 | 
			
		||||
	launch_arg<T, U, Args...> *launchargs = (launch_arg<T, U,  Args...> *) arg;
 | 
			
		||||
	int ret = exile_enable_policy(launchargs->policy);
 | 
			
		||||
	if(ret != 0)
 | 
			
		||||
	{
 | 
			
		||||
		EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	T result = std::apply(launchargs->fn, launchargs->args);
 | 
			
		||||
	std::cout << result;
 | 
			
		||||
	memcpy(launchargs->result_shm, &result, sizeof(T));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
int exile_clone_handle_serializer(void * arg)
 | 
			
		||||
{
 | 
			
		||||
	static_assert(std::is_copy_constructible_v<T>);
 | 
			
		||||
 | 
			
		||||
	launch_arg_serializer<T, U, Args...> *launchargs = (launch_arg_serializer<T, U, Args...> *) arg;
 | 
			
		||||
	int ret = exile_enable_policy(launchargs->policy);
 | 
			
		||||
	if(ret != 0)
 | 
			
		||||
	{
 | 
			
		||||
		EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	T result = std::apply(launchargs->fn, launchargs->args);
 | 
			
		||||
	/* TODO: exception handling */
 | 
			
		||||
	/* TODO: ugly :S */
 | 
			
		||||
	char *target = launchargs->serialize_buffer + sizeof(size_t);
 | 
			
		||||
	size_t n = launchargs->n - sizeof(size_t);
 | 
			
		||||
 | 
			
		||||
	size_t size = launchargs->serializer(result, target, n);
 | 
			
		||||
	memcpy(launchargs->serialize_buffer, &size, sizeof(size_t));
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int do_clone(int (*clonefn)(void *), void *launcharg)
 | 
			
		||||
{
 | 
			
		||||
	struct rlimit rlimit;
 | 
			
		||||
	int ret = getrlimit(RLIMIT_STACK, &rlimit);
 | 
			
		||||
	if(ret != 0)
 | 
			
		||||
	{
 | 
			
		||||
		EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno));
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
	size_t size = rlimit.rlim_cur;
 | 
			
		||||
	char *stack = (char *) calloc(1, size);
 | 
			
		||||
	if(stack == NULL)
 | 
			
		||||
	{
 | 
			
		||||
		EXILE_LOG_ERROR("Failed to allocate stack memory for child\n");
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	stack += size;
 | 
			
		||||
 | 
			
		||||
	ret = clone(clonefn, stack, 17 /* SIGCHLD */, launcharg);
 | 
			
		||||
	int status = 0;
 | 
			
		||||
	waitpid(ret, &status, __WALL);
 | 
			
		||||
	if(WIFEXITED(status))
 | 
			
		||||
	{
 | 
			
		||||
		return WEXITSTATUS(status);
 | 
			
		||||
	}
 | 
			
		||||
	/* TODO: exception or what? */
 | 
			
		||||
	return 23;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
typename std::enable_if_t<std::is_trivially_copyable_v<T>, T> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
 | 
			
		||||
{
 | 
			
		||||
	size_t mapsize = sizeof(T);
 | 
			
		||||
	T * sharedbuf =  (T *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
 | 
			
		||||
	if(sharedbuf == NULL)
 | 
			
		||||
	{
 | 
			
		||||
		throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
 | 
			
		||||
	launch_arg<T, U, Args...> launcharg(policy, sharedbuf, fn, std::forward<Args>(args)...);
 | 
			
		||||
 | 
			
		||||
	int (*clonefn)(void *) = &exile_clone_handle_trivial<T, U, Args...>;
 | 
			
		||||
	/* TODO: exception or what? */
 | 
			
		||||
	int ret = do_clone(clonefn, &launcharg);
 | 
			
		||||
	if(ret == 0)
 | 
			
		||||
	{
 | 
			
		||||
		return *sharedbuf;
 | 
			
		||||
	}
 | 
			
		||||
	throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
 | 
			
		||||
	return T();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
typename std::enable_if_t<!std::is_trivially_copyable_v<T> && std::is_copy_constructible_v<T>, T>
 | 
			
		||||
	exile_launch(struct exile_policy *policy, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args)
 | 
			
		||||
{
 | 
			
		||||
	size_t mapsize = EXILE_MMAP_SIZE;
 | 
			
		||||
	char *sharedbuf =  (char *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
 | 
			
		||||
	if(sharedbuf == NULL)
 | 
			
		||||
	{
 | 
			
		||||
		throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
 | 
			
		||||
	}
 | 
			
		||||
	std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	launch_arg_serializer<T, U, Args...> launcharg(policy, sharedbuf, mapsize, serializer, deserializer, fn,												   std::forward<Args>(args)...);
 | 
			
		||||
 | 
			
		||||
	int (*clonefn)(void *) = &exile_clone_handle_serializer<T, U, Args...>;
 | 
			
		||||
	/* TODO: exception or what? */
 | 
			
		||||
	int ret = do_clone(clonefn, &launcharg);
 | 
			
		||||
	if(ret == 0)
 | 
			
		||||
	{
 | 
			
		||||
		size_t size = 0;
 | 
			
		||||
		memcpy(&size, sharedbuf, sizeof(size));
 | 
			
		||||
 | 
			
		||||
		return deserializer(sharedbuf + sizeof(size_t), size);
 | 
			
		||||
	}
 | 
			
		||||
	throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
 | 
			
		||||
	return T();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class T>
 | 
			
		||||
std::basic_string<typename T::value_type> deserialize_stdstring(const char *buf, size_t n)
 | 
			
		||||
{
 | 
			
		||||
	return std::basic_string<typename T::value_type> { buf, n };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class T>
 | 
			
		||||
size_t serialize_stdstring(const std::basic_string<typename T::value_type> &t, char *buf, size_t n)
 | 
			
		||||
{
 | 
			
		||||
	if(n < t.size())
 | 
			
		||||
	{
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	memcpy(buf, t.data(), t.size());
 | 
			
		||||
	return t.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template<typename T, typename U, typename ... Args>
 | 
			
		||||
std::basic_string<typename T::value_type> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
 | 
			
		||||
{
 | 
			
		||||
	return exile_launch<T, U, Args...>(policy, &serialize_stdstring<T>, &deserialize_stdstring<T>, fn, std::forward<Args>(args) ...);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								test.c
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								test.c
									
									
									
									
									
								
							@@ -489,6 +489,7 @@ int test_no_new_fds()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern int mkpath(const char *p, mode_t mode, int baseisfile);
 | 
			
		||||
int test_mkpath()
 | 
			
		||||
{
 | 
			
		||||
	system("rm -rf /tmp/.exile.h/");
 | 
			
		||||
@@ -547,12 +548,14 @@ int test_fail_flags()
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int *read_pipe = NULL;
 | 
			
		||||
int do_launch_test(void *arg)
 | 
			
		||||
{
 | 
			
		||||
	int num = *(int *)(arg);
 | 
			
		||||
	num += 1;
 | 
			
		||||
	char buffer[512] = { 0 };
 | 
			
		||||
	read(child_write_pipe[0], buffer, sizeof(buffer)-1);
 | 
			
		||||
	read(*read_pipe, buffer, sizeof(buffer)-1);
 | 
			
		||||
	printf("Sandboxed +1: %i\n", num);
 | 
			
		||||
	printf("Echoing: %s\n", buffer);
 | 
			
		||||
	fflush(stdout);
 | 
			
		||||
@@ -568,6 +571,7 @@ int test_launch()
 | 
			
		||||
	params.func = &do_launch_test;
 | 
			
		||||
	params.funcarg = #
 | 
			
		||||
	params.policy = policy;
 | 
			
		||||
	read_pipe = ¶ms.child_write_pipe[0];
 | 
			
		||||
	int launchfd = exile_launch(¶ms, &res);
 | 
			
		||||
	if(launchfd < 0)
 | 
			
		||||
	{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								test.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										92
									
								
								test.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
#include "exile.hpp"
 | 
			
		||||
#include "assert.h"
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
std::string sandboxed_reverse(std::string str)
 | 
			
		||||
{
 | 
			
		||||
	std::reverse(str.begin(), str.end());
 | 
			
		||||
	return str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t stdstrlen(const std::string &str)
 | 
			
		||||
{
 | 
			
		||||
	return str.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int incrementer(int arg)
 | 
			
		||||
{
 | 
			
		||||
	return ++arg;
 | 
			
		||||
}
 | 
			
		||||
int test_exile_launch_trivial()
 | 
			
		||||
{
 | 
			
		||||
	int u = 22;
 | 
			
		||||
	int result = exile_launch<int>(exile_init_policy(), &incrementer, u);
 | 
			
		||||
	assert(result == 23);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int test_exile_launch_stdstring()
 | 
			
		||||
{
 | 
			
		||||
	std::string str = "abc123";
 | 
			
		||||
	std::string reversed = exile_launch<std::string>(exile_init_policy(), &sandboxed_reverse, str);
 | 
			
		||||
	assert(reversed == "321cba");
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct not_trivially_copyable
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	std::string somecontent;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int test_exile_launch_serializer()
 | 
			
		||||
{
 | 
			
		||||
	static_assert(! std::is_trivially_copyable_v<not_trivially_copyable>);
 | 
			
		||||
 | 
			
		||||
	auto serializer = [](const not_trivially_copyable &obj, char *buf, size_t n){
 | 
			
		||||
		serialize_stdstring<std::string>(obj.somecontent, buf, n);
 | 
			
		||||
		return obj.somecontent.size();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto deserializer = [](const char *buffer, size_t n) {
 | 
			
		||||
		not_trivially_copyable obj;
 | 
			
		||||
		obj.somecontent = deserialize_stdstring<std::string>(buffer, n);
 | 
			
		||||
		return obj;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	not_trivially_copyable result = exile_launch<not_trivially_copyable>(exile_init_policy(), serializer, deserializer, []() {not_trivially_copyable obj; obj.somecontent = "Just something"; return obj;});
 | 
			
		||||
 | 
			
		||||
	assert(result.somecontent == "Just something");
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	if(argc < 2)
 | 
			
		||||
	{
 | 
			
		||||
		std::cerr << "Missing test" << std::endl;
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	std::map<std::string, int (*)()> map = {
 | 
			
		||||
		{ "launch-trivial-cpp", &test_exile_launch_trivial} ,
 | 
			
		||||
		{ "launch-stdstring-cpp", &test_exile_launch_stdstring },
 | 
			
		||||
		{ "launch-serializer-cpp", &test_exile_launch_serializer },
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::string test = argv[1];
 | 
			
		||||
	if(test == "--dumptests")
 | 
			
		||||
	{
 | 
			
		||||
		for(auto &entry : map)
 | 
			
		||||
		{
 | 
			
		||||
			std::cout << entry.first << std::endl;
 | 
			
		||||
		}
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	int (*fn)() = map[test];
 | 
			
		||||
	if(fn != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		return fn();
 | 
			
		||||
	}
 | 
			
		||||
	std::cerr << "Unknown test" << std::endl;
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								test.sh
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								test.sh
									
									
									
									
									
								
							@@ -44,14 +44,15 @@ function runtest_skipped()
 | 
			
		||||
 | 
			
		||||
function runtest()
 | 
			
		||||
{
 | 
			
		||||
	testname="$1"
 | 
			
		||||
	test_log_file="$2"
 | 
			
		||||
	testbin="$1"
 | 
			
		||||
	testname="$2"
 | 
			
		||||
	test_log_file="$3"
 | 
			
		||||
 | 
			
		||||
	echo "Running: $testname. Date: $(date)" > "${test_log_file}"
 | 
			
		||||
 | 
			
		||||
	echo -n "Running $1... "
 | 
			
		||||
	echo -n "Running $testname... "
 | 
			
		||||
	#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
 | 
			
		||||
	(./test $1 || exit $?) &>> "${test_log_file}"
 | 
			
		||||
	(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
 | 
			
		||||
	ret=$?
 | 
			
		||||
	SUCCESS="no"
 | 
			
		||||
	if [ $ret -eq 0 ] ; then
 | 
			
		||||
@@ -64,7 +65,7 @@ function runtest()
 | 
			
		||||
		runtest_fail
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
	echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
 | 
			
		||||
	echo "Finished: ${testname} (${testbin}). Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GIT_ID=$( git log --pretty="format:%h" -n1 )
 | 
			
		||||
@@ -79,7 +80,12 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/exile_test_${GIT_ID}_${TIMESTAMP}"
 | 
			
		||||
 | 
			
		||||
for test in $( ./test --dumptests ) ; do
 | 
			
		||||
	testname=$( echo $test )
 | 
			
		||||
	runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
 | 
			
		||||
	runtest test "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
for test in $( ./testcpp --dumptests ) ; do
 | 
			
		||||
	testname=$( echo $test )
 | 
			
		||||
	runtest testcpp "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
 | 
			
		||||
done
 | 
			
		||||
echo
 | 
			
		||||
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"
 | 
			
		||||
 
 | 
			
		||||
		Tagairt in Eagrán Nua
	
	Cuir bac ar úsáideoir