From 87725a3a9ee70e4e30f3b1ffeff88d95732f76db Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 24 Feb 2025 01:05:45 +0100 Subject: [PATCH] Create a unit7 for all unit tests. --- lib7/std/collections/enummap.c3 | 10 - lib7/std/collections/object.c3 | 2 +- lib7/std/collections/ringbuffer.c3 | 33 +- lib7/std/core/test.c3 | 2 +- lib7/std/encoding/csv.c3 | 4 +- lib7/std/encoding/json.c3 | 4 +- lib7/std/net/url.c3 | 136 ++-- test/unit7/regression/any.c3 | 25 + test/unit7/regression/bitstruct_ops.c3 | 44 ++ test/unit7/regression/bitstruct_ops2.c3 | 65 ++ test/unit7/regression/bitstruct_ops3.c3 | 90 +++ test/unit7/regression/cast_slice_to_arr.c3 | 17 + test/unit7/regression/castable_assignable.c3 | 24 + test/unit7/regression/catch_err.c3 | 21 + test/unit7/regression/copysign.c3 | 15 + test/unit7/regression/ct_slice.c3 | 38 ++ test/unit7/regression/distinct_inline.c3 | 49 ++ test/unit7/regression/faults.c3 | 21 + .../file_line_func_module_builtins.c3 | 22 + test/unit7/regression/gather_scatter.c3 | 16 + test/unit7/regression/inc_dec.c3 | 34 + test/unit7/regression/int128.c3 | 21 + test/unit7/regression/int_min.c3 | 9 + test/unit7/regression/liveness_any.c3 | 22 + test/unit7/regression/lvalue_handling.c3 | 21 + test/unit7/regression/masked_load_store.c3 | 28 + test/unit7/regression/methodsof.c3 | 80 +++ test/unit7/regression/pointer_diff.c3 | 20 + test/unit7/regression/pointer_non_decay.c3 | 19 + test/unit7/regression/select.c3 | 10 + .../regression/signed_unsigned_compare.c3 | 157 +++++ test/unit7/regression/slice_assign.c3 | 9 + test/unit7/regression/struct_alignment.c3 | 18 + test/unit7/regression/subscripting.c3 | 15 + test/unit7/regression/subtype.c3 | 91 +++ test/unit7/regression/swizzle.c3 | 18 + test/unit7/regression/ternary.c3 | 11 + test/unit7/regression/unwrapping.c3 | 19 + test/unit7/regression/vecpointer.c3 | 35 + test/unit7/regression/vector_conversion.c3 | 31 + test/unit7/regression/vector_method_reduce.c3 | 18 + test/unit7/regression/vector_ops.c3 | 114 ++++ test/unit7/stdlib/atomic.c3 | 245 +++++++ test/unit7/stdlib/atomic_types.c3 | 156 +++++ test/unit7/stdlib/collections/bitset.c3 | 89 +++ test/unit7/stdlib/collections/copy_map.c3 | 53 ++ .../unit7/stdlib/collections/elastic_array.c3 | 143 ++++ test/unit7/stdlib/collections/enummap.c3 | 29 + test/unit7/stdlib/collections/generic_list.c3 | 15 + test/unit7/stdlib/collections/linkedlist.c3 | 237 +++++++ test/unit7/stdlib/collections/list.c3 | 178 +++++ test/unit7/stdlib/collections/map.c3 | 105 +++ test/unit7/stdlib/collections/object.c3 | 43 ++ .../unit7/stdlib/collections/priorityqueue.c3 | 62 ++ test/unit7/stdlib/collections/range.c3 | 36 + test/unit7/stdlib/collections/ringbuffer.c3 | 29 + test/unit7/stdlib/compression/qoi.c3 | 38 ++ test/unit7/stdlib/conv_tests.c3 | 79 +++ test/unit7/stdlib/core/array.c3 | 32 + test/unit7/stdlib/core/bitorder.c3 | 45 ++ test/unit7/stdlib/core/builtintests.c3 | 79 +++ test/unit7/stdlib/core/comparison.c3 | 22 + test/unit7/stdlib/core/dstring.c3 | 277 ++++++++ test/unit7/stdlib/core/formatter.c3 | 20 + test/unit7/stdlib/core/mem_allocator.c3 | 25 + test/unit7/stdlib/core/runtime.c3 | 37 ++ test/unit7/stdlib/core/string.c3 | 208 ++++++ test/unit7/stdlib/core/string_iterator.c3 | 35 + test/unit7/stdlib/core/test_test.c3 | 332 ++++++++++ test/unit7/stdlib/core/values.c3 | 9 + test/unit7/stdlib/crypto/rc4.c3 | 16 + test/unit7/stdlib/encoding/base32.c3 | 108 +++ test/unit7/stdlib/encoding/base64.c3 | 122 ++++ test/unit7/stdlib/encoding/csv.c3 | 68 ++ test/unit7/stdlib/encoding/hex.c3 | 49 ++ test/unit7/stdlib/encoding/json.c3 | 53 ++ test/unit7/stdlib/hash/fnv32a.c3 | 24 + test/unit7/stdlib/hash/fnv64a.c3 | 24 + test/unit7/stdlib/hash/md5.c3 | 41 ++ test/unit7/stdlib/hash/sha1.c3 | 70 ++ test/unit7/stdlib/hash/sha256.c3 | 74 +++ test/unit7/stdlib/io/bits.c3 | 249 +++++++ test/unit7/stdlib/io/bufferstream.c3 | 94 +++ test/unit7/stdlib/io/bytebuffer.c3 | 29 + test/unit7/stdlib/io/bytestream.c3 | 72 ++ test/unit7/stdlib/io/dstringstream.c3 | 18 + test/unit7/stdlib/io/file_read_write_any.c3 | 94 +++ test/unit7/stdlib/io/fileinfo.c3 | 16 + test/unit7/stdlib/io/limitreader.c3 | 20 + test/unit7/stdlib/io/multireader.c3 | 21 + test/unit7/stdlib/io/multiwriter.c3 | 17 + test/unit7/stdlib/io/path.c3 | 378 +++++++++++ test/unit7/stdlib/io/printf.c3 | 116 ++++ test/unit7/stdlib/io/scanner.c3 | 68 ++ test/unit7/stdlib/io/stream.c3 | 89 +++ test/unit7/stdlib/io/teereader.c3 | 17 + test/unit7/stdlib/io/varint.c3 | 54 ++ test/unit7/stdlib/macros/core_builtins.c3 | 23 + test/unit7/stdlib/math/bigint.c3 | 75 +++ test/unit7/stdlib/math/frexp_signbit.c3 | 18 + test/unit7/stdlib/math/math.c3 | 614 ++++++++++++++++++ test/unit7/stdlib/math/math_complex.c3 | 59 ++ test/unit7/stdlib/math/math_is_even_odd.c3 | 23 + test/unit7/stdlib/math/math_vector.c3 | 125 ++++ test/unit7/stdlib/math/matrix.c3 | 122 ++++ test/unit7/stdlib/math/quaternion.c3 | 38 ++ test/unit7/stdlib/math/random.c3 | 35 + test/unit7/stdlib/mem/temp_mem.c3 | 68 ++ test/unit7/stdlib/net/inetaddr.c3 | 49 ++ test/unit7/stdlib/net/url.c3 | 459 +++++++++++++ test/unit7/stdlib/net/url_encoding.c3 | 141 ++++ test/unit7/stdlib/os/env.c3 | 17 + test/unit7/stdlib/sort/binarysearch.c3 | 37 ++ test/unit7/stdlib/sort/countingsort.c3 | 100 +++ test/unit7/stdlib/sort/insertionsort.c3 | 102 +++ test/unit7/stdlib/sort/quickselect.c3 | 63 ++ test/unit7/stdlib/sort/quicksort.c3 | 102 +++ test/unit7/stdlib/sort/sort.c3 | 22 + test/unit7/stdlib/sort/sorted.c3 | 92 +++ test/unit7/stdlib/string.c3 | 20 + test/unit7/stdlib/string_to_float.c3 | 29 + test/unit7/stdlib/threads/channel.c3 | 337 ++++++++++ test/unit7/stdlib/threads/mutex.c3 | 147 +++++ test/unit7/stdlib/threads/pool.c3 | 79 +++ test/unit7/stdlib/threads/simple_thread.c3 | 90 +++ test/unit7/stdlib/time/datetime.c3 | 151 +++++ test/unit7/stdlib/time/format.c3 | 51 ++ test/unit7/stdlib/time/time.c3 | 29 + 128 files changed, 9311 insertions(+), 103 deletions(-) create mode 100644 test/unit7/regression/any.c3 create mode 100644 test/unit7/regression/bitstruct_ops.c3 create mode 100644 test/unit7/regression/bitstruct_ops2.c3 create mode 100644 test/unit7/regression/bitstruct_ops3.c3 create mode 100644 test/unit7/regression/cast_slice_to_arr.c3 create mode 100644 test/unit7/regression/castable_assignable.c3 create mode 100644 test/unit7/regression/catch_err.c3 create mode 100644 test/unit7/regression/copysign.c3 create mode 100644 test/unit7/regression/ct_slice.c3 create mode 100644 test/unit7/regression/distinct_inline.c3 create mode 100644 test/unit7/regression/faults.c3 create mode 100644 test/unit7/regression/file_line_func_module_builtins.c3 create mode 100644 test/unit7/regression/gather_scatter.c3 create mode 100644 test/unit7/regression/inc_dec.c3 create mode 100644 test/unit7/regression/int128.c3 create mode 100644 test/unit7/regression/int_min.c3 create mode 100644 test/unit7/regression/liveness_any.c3 create mode 100644 test/unit7/regression/lvalue_handling.c3 create mode 100644 test/unit7/regression/masked_load_store.c3 create mode 100644 test/unit7/regression/methodsof.c3 create mode 100644 test/unit7/regression/pointer_diff.c3 create mode 100644 test/unit7/regression/pointer_non_decay.c3 create mode 100644 test/unit7/regression/select.c3 create mode 100644 test/unit7/regression/signed_unsigned_compare.c3 create mode 100644 test/unit7/regression/slice_assign.c3 create mode 100644 test/unit7/regression/struct_alignment.c3 create mode 100644 test/unit7/regression/subscripting.c3 create mode 100644 test/unit7/regression/subtype.c3 create mode 100644 test/unit7/regression/swizzle.c3 create mode 100644 test/unit7/regression/ternary.c3 create mode 100644 test/unit7/regression/unwrapping.c3 create mode 100644 test/unit7/regression/vecpointer.c3 create mode 100644 test/unit7/regression/vector_conversion.c3 create mode 100644 test/unit7/regression/vector_method_reduce.c3 create mode 100644 test/unit7/regression/vector_ops.c3 create mode 100644 test/unit7/stdlib/atomic.c3 create mode 100644 test/unit7/stdlib/atomic_types.c3 create mode 100644 test/unit7/stdlib/collections/bitset.c3 create mode 100644 test/unit7/stdlib/collections/copy_map.c3 create mode 100644 test/unit7/stdlib/collections/elastic_array.c3 create mode 100644 test/unit7/stdlib/collections/enummap.c3 create mode 100644 test/unit7/stdlib/collections/generic_list.c3 create mode 100644 test/unit7/stdlib/collections/linkedlist.c3 create mode 100644 test/unit7/stdlib/collections/list.c3 create mode 100644 test/unit7/stdlib/collections/map.c3 create mode 100644 test/unit7/stdlib/collections/object.c3 create mode 100644 test/unit7/stdlib/collections/priorityqueue.c3 create mode 100644 test/unit7/stdlib/collections/range.c3 create mode 100644 test/unit7/stdlib/collections/ringbuffer.c3 create mode 100644 test/unit7/stdlib/compression/qoi.c3 create mode 100644 test/unit7/stdlib/conv_tests.c3 create mode 100644 test/unit7/stdlib/core/array.c3 create mode 100644 test/unit7/stdlib/core/bitorder.c3 create mode 100644 test/unit7/stdlib/core/builtintests.c3 create mode 100644 test/unit7/stdlib/core/comparison.c3 create mode 100644 test/unit7/stdlib/core/dstring.c3 create mode 100644 test/unit7/stdlib/core/formatter.c3 create mode 100644 test/unit7/stdlib/core/mem_allocator.c3 create mode 100644 test/unit7/stdlib/core/runtime.c3 create mode 100644 test/unit7/stdlib/core/string.c3 create mode 100644 test/unit7/stdlib/core/string_iterator.c3 create mode 100644 test/unit7/stdlib/core/test_test.c3 create mode 100644 test/unit7/stdlib/core/values.c3 create mode 100644 test/unit7/stdlib/crypto/rc4.c3 create mode 100644 test/unit7/stdlib/encoding/base32.c3 create mode 100644 test/unit7/stdlib/encoding/base64.c3 create mode 100644 test/unit7/stdlib/encoding/csv.c3 create mode 100644 test/unit7/stdlib/encoding/hex.c3 create mode 100644 test/unit7/stdlib/encoding/json.c3 create mode 100644 test/unit7/stdlib/hash/fnv32a.c3 create mode 100644 test/unit7/stdlib/hash/fnv64a.c3 create mode 100644 test/unit7/stdlib/hash/md5.c3 create mode 100644 test/unit7/stdlib/hash/sha1.c3 create mode 100644 test/unit7/stdlib/hash/sha256.c3 create mode 100644 test/unit7/stdlib/io/bits.c3 create mode 100644 test/unit7/stdlib/io/bufferstream.c3 create mode 100644 test/unit7/stdlib/io/bytebuffer.c3 create mode 100644 test/unit7/stdlib/io/bytestream.c3 create mode 100644 test/unit7/stdlib/io/dstringstream.c3 create mode 100644 test/unit7/stdlib/io/file_read_write_any.c3 create mode 100644 test/unit7/stdlib/io/fileinfo.c3 create mode 100644 test/unit7/stdlib/io/limitreader.c3 create mode 100644 test/unit7/stdlib/io/multireader.c3 create mode 100644 test/unit7/stdlib/io/multiwriter.c3 create mode 100644 test/unit7/stdlib/io/path.c3 create mode 100644 test/unit7/stdlib/io/printf.c3 create mode 100644 test/unit7/stdlib/io/scanner.c3 create mode 100644 test/unit7/stdlib/io/stream.c3 create mode 100644 test/unit7/stdlib/io/teereader.c3 create mode 100644 test/unit7/stdlib/io/varint.c3 create mode 100644 test/unit7/stdlib/macros/core_builtins.c3 create mode 100644 test/unit7/stdlib/math/bigint.c3 create mode 100644 test/unit7/stdlib/math/frexp_signbit.c3 create mode 100644 test/unit7/stdlib/math/math.c3 create mode 100644 test/unit7/stdlib/math/math_complex.c3 create mode 100644 test/unit7/stdlib/math/math_is_even_odd.c3 create mode 100644 test/unit7/stdlib/math/math_vector.c3 create mode 100644 test/unit7/stdlib/math/matrix.c3 create mode 100644 test/unit7/stdlib/math/quaternion.c3 create mode 100644 test/unit7/stdlib/math/random.c3 create mode 100644 test/unit7/stdlib/mem/temp_mem.c3 create mode 100644 test/unit7/stdlib/net/inetaddr.c3 create mode 100644 test/unit7/stdlib/net/url.c3 create mode 100644 test/unit7/stdlib/net/url_encoding.c3 create mode 100644 test/unit7/stdlib/os/env.c3 create mode 100644 test/unit7/stdlib/sort/binarysearch.c3 create mode 100644 test/unit7/stdlib/sort/countingsort.c3 create mode 100644 test/unit7/stdlib/sort/insertionsort.c3 create mode 100644 test/unit7/stdlib/sort/quickselect.c3 create mode 100644 test/unit7/stdlib/sort/quicksort.c3 create mode 100644 test/unit7/stdlib/sort/sort.c3 create mode 100644 test/unit7/stdlib/sort/sorted.c3 create mode 100644 test/unit7/stdlib/string.c3 create mode 100644 test/unit7/stdlib/string_to_float.c3 create mode 100644 test/unit7/stdlib/threads/channel.c3 create mode 100644 test/unit7/stdlib/threads/mutex.c3 create mode 100644 test/unit7/stdlib/threads/pool.c3 create mode 100644 test/unit7/stdlib/threads/simple_thread.c3 create mode 100644 test/unit7/stdlib/time/datetime.c3 create mode 100644 test/unit7/stdlib/time/format.c3 create mode 100644 test/unit7/stdlib/time/time.c3 diff --git a/lib7/std/collections/enummap.c3 b/lib7/std/collections/enummap.c3 index 1c5437d14..98b7974b0 100644 --- a/lib7/std/collections/enummap.c3 +++ b/lib7/std/collections/enummap.c3 @@ -28,16 +28,6 @@ fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic return n; } -fn String EnumMap.to_string(&self, Allocator allocator) @dynamic -{ - return string::format("%s", *self, allocator: allocator); -} - -fn String EnumMap.to_tstring(&self) @dynamic -{ - return string::tformat("%s", *self); -} - <* @return "The total size of this map, which is the same as the number of enum values" @pure diff --git a/lib7/std/collections/object.c3 b/lib7/std/collections/object.c3 index a4434bc3b..a0697865a 100644 --- a/lib7/std/collections/object.c3 +++ b/lib7/std/collections/object.c3 @@ -156,7 +156,7 @@ fn void Object.init_map_if_needed(&self) @private if (self.is_empty()) { self.type = ObjectInternalMap.typeid; - self.map.tinit(); + self.map.init(self.allocator); } } diff --git a/lib7/std/collections/ringbuffer.c3 b/lib7/std/collections/ringbuffer.c3 index 33f2fce0f..a164b2226 100644 --- a/lib7/std/collections/ringbuffer.c3 +++ b/lib7/std/collections/ringbuffer.c3 @@ -2,10 +2,11 @@ @require Type.kindof == ARRAY : "Required an array type" *> module std::collections::ringbuffer(); +import std::io; def Element = $typeof((Type){}[0]); -struct RingBuffer +struct RingBuffer (Printable) { Type buf; usz written; @@ -19,7 +20,7 @@ fn void RingBuffer.init(&self) @inline fn void RingBuffer.push(&self, Element c) { - if (self.written < buf.len) + if (self.written < self.buf.len) { self.buf[self.written] = c; self.written++; @@ -27,14 +28,14 @@ fn void RingBuffer.push(&self, Element c) else { self.buf[self.head] = c; - self.head = (self.head + 1) % buf.len; + self.head = (self.head + 1) % self.buf.len; } } fn Element RingBuffer.get(&self, usz index) @operator([]) { - index %= buf.len; - usz avail = buf.len - self.head; + index %= self.buf.len; + usz avail = self.buf.len - self.head; if (index < avail) { return self.buf[self.head + index]; @@ -48,25 +49,25 @@ fn Element! RingBuffer.pop(&self) { case self.written == 0: return SearchResult.MISSING?; - case self.written < buf.len: + case self.written < self.buf.len: self.written--; return self.buf[self.written]; default: - self.head = (self.head - 1) % buf.len; + self.head = (self.head - 1) % self.buf.len; return self.buf[self.head]; } } -fn usz! RingBuffer.to_format(&self, Formatter* format) +fn usz! RingBuffer.to_format(&self, Formatter* format) @dynamic { // Improve this? - return format.printnf("%s", self.buf); + return format.printf("%s", self.buf); } fn usz RingBuffer.read(&self, usz index, Element[] buffer) { - index %= buf.len; - if (self.written < buf.len) + index %= self.buf.len; + if (self.written < self.buf.len) { if (index >= self.written) return 0; usz end = self.written - index; @@ -74,7 +75,7 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer) buffer[:n] = self.buf[index:n]; return n; } - usz end = buf.len - self.head; + usz end = self.buf.len - self.head; if (index >= end) { index -= end; @@ -83,13 +84,13 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer) buffer[:n] = self.buf[index:n]; return n; } - if (buffer.len <= buf.len - index) + if (buffer.len <= self.buf.len - index) { usz n = buffer.len; buffer[:n] = self.buf[self.head + index:n]; return n; } - usz n1 = buf.len - index; + usz n1 = self.buf.len - index; buffer[:n1] = self.buf[self.head + index:n1]; buffer = buffer[n1..]; index -= n1; @@ -101,7 +102,7 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer) fn void RingBuffer.write(&self, Element[] buffer) { usz i; - while (self.written < buf.len && i < buffer.len) + while (self.written < self.buf.len && i < buffer.len) { self.buf[self.written] = buffer[i++]; self.written++; @@ -109,6 +110,6 @@ fn void RingBuffer.write(&self, Element[] buffer) foreach (c : buffer[i..]) { self.buf[self.head] = c; - self.head = (self.head + 1) % buf.len; + self.head = (self.head + 1) % self.buf.len; } } \ No newline at end of file diff --git a/lib7/std/core/test.c3 b/lib7/std/core/test.c3 index 0383e374a..0f183fa24 100644 --- a/lib7/std/core/test.c3 +++ b/lib7/std/core/test.c3 @@ -72,7 +72,7 @@ macro @check(#condition, String format = "", args...) @stack_mem(512; Allocator allocator) { DString s; - s.new_init(allocator: allocator); + s.init(allocator); s.appendf("check `%s` failed. ", $stringify(#condition)); s.appendf(format, ...args); print_panicf(s.str_view()); diff --git a/lib7/std/encoding/csv.c3 b/lib7/std/encoding/csv.c3 index 42e077a54..b24380c29 100644 --- a/lib7/std/encoding/csv.c3 +++ b/lib7/std/encoding/csv.c3 @@ -77,13 +77,13 @@ macro void! CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @ { @stack_mem(512; Allocator mem) { - String! s = io::readline(stream, mem); + String! s = io::readline(mem, stream); if (catch err = s) { if (err == IoError.EOF) return; return err?; } - @body(s.split(sep, allocator: mem)); + @body(s.split(mem, sep)); }; } } \ No newline at end of file diff --git a/lib7/std/encoding/json.c3 b/lib7/std/encoding/json.c3 index a26bf87ec..648dc98fb 100644 --- a/lib7/std/encoding/json.c3 +++ b/lib7/std/encoding/json.c3 @@ -27,9 +27,9 @@ fn Object*! tparse_string(String s) fn Object*! parse(Allocator allocator, InStream s) { - @stack_mem(512; Allocator mem) + @stack_mem(512; Allocator smem) { - JsonContext context = { .last_string = dstring::new_with_capacity(mem, 64), .stream = s, .allocator = allocator }; + JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator }; @pool(allocator) { return parse_any(&context); diff --git a/lib7/std/net/url.c3 b/lib7/std/net/url.c3 index b242d54c8..ad500071c 100644 --- a/lib7/std/net/url.c3 +++ b/lib7/std/net/url.c3 @@ -170,81 +170,81 @@ fn Url! parse(Allocator allocator, String url_string) return url; } -<* - Stringify a Url struct. - - @param [in] self - @param [inout] allocator - @return "Url as a string" -*> -fn String Url.to_string(&self, Allocator allocator = allocator::heap()) @dynamic => @pool(allocator) +fn usz! Url.to_format(&self, Formatter* f) @dynamic { - DString builder = dstring::tnew(); - + usz len; // Add scheme if it exists if (self.scheme != "") { - builder.append_chars(self.scheme); - builder.append_char(':'); - if (self.host.len > 0) builder.append_chars("//"); + len += f.print(self.scheme)!; + len += f.print(":")!; + if (self.host.len > 0) len += f.print("//")!; } // Add username and password if they exist - if (self.username != "") + if (self.username) { - String username = tencode(self.username, USERPASS); - builder.append_chars(username); - - if (self.password != "") + @stack_mem(64; Allocator smem) { - builder.append_char(':'); - - String password = tencode(self.password, USERPASS); - builder.append_chars(password); + len += f.print(encode(smem, self.username, USERPASS))!; + }; + if (self.password) + { + len += f.print(":")!; + @stack_mem(64; Allocator smem) + { + len += f.print(encode(smem, self.password, USERPASS))!; + }; } - builder.append_char('@'); + len += f.print("@")!; } // Add host - String host = tencode(self.host, HOST); - builder.append_chars(host); + @stack_mem(128; Allocator smem) + { + len += f.print(encode(smem, self.host, HOST))!; + }; // Add port - if (self.port != 0) - { - builder.append_char(':'); - builder.appendf("%d", self.port); - } + if (self.port) len += f.printf(":%d", self.port)!; // Add path - String path = tencode(self.path, PATH); - builder.append_chars(path); + @stack_mem(256; Allocator smem) + { + len += f.print(encode(smem, self.path, PATH))!; + }; // Add query if it exists (note that `query` is expected to // be already properly encoded). - if (self.query != "") + if (self.query) { - builder.append_char('?'); - builder.append_chars(self.query); + len += f.print("?")!; + len += f.print(self.query)!; } // Add fragment if it exists - if (self.fragment != "") + if (self.fragment) { - builder.append_char('#'); - - String fragment = tencode(self.fragment, FRAGMENT); - builder.append_chars(fragment); + @stack_mem(256; Allocator smem) + { + len += f.print("#")!; + len += f.print(encode(smem, self.fragment, FRAGMENT))!; + }; } - - return builder.copy_str(allocator); + return len; } -def UrlQueryValueList = List(); +fn String Url.to_string(&self, Allocator allocator) +{ + return string::format(allocator, "%s", *self); +} + + +def UrlQueryValueList = List{String}; struct UrlQueryValues { - inline HashMap() map; + inline HashMap{String, UrlQueryValueList} map; UrlQueryValueList key_order; } @@ -317,41 +317,35 @@ fn UrlQueryValues* UrlQueryValues.add(&self, String key, String value) } -<* - Stringify UrlQueryValues into an encoded query string. - @param [in] self - @param [inout] allocator - @return "a percent-encoded query string" -*> -fn String UrlQueryValues.to_string(&self, Allocator allocator = allocator::heap()) @dynamic => @pool(allocator) +fn usz! UrlQueryValues.to_format(&self, Formatter* f) @dynamic { - DString builder = dstring::tnew(); - + usz len; usz i; foreach (key: self.key_order) { - String encoded_key = tencode(key, QUERY); - - UrlQueryValueList! values = self.map.get(key); - if (catch values) continue; - - foreach (value: values) + @stack_mem(128; Allocator mem) { - if (i > 0) builder.append_char('&'); - - builder.append_chars(encoded_key); - builder.append_char('='); - - String encoded_value = tencode(value, QUERY); - builder.append_chars(encoded_value); - i++; - } - }; - - return builder.copy_str(allocator); + String encoded_key = encode(mem, key, QUERY); + UrlQueryValueList! values = self.map.get(key); + if (catch values) continue; + foreach (value : values) + { + if (i > 0) len += f.print("&")!; + len += f.print(encoded_key)!; + len += f.print("=")!; + @stack_mem(256; Allocator smem) + { + len += f.print(encode(smem, value, QUERY))!; + }; + i++; + } + }; + } + return len; } + fn void UrlQueryValues.free(&self) { self.map.@each(;String key, UrlQueryValueList values) diff --git a/test/unit7/regression/any.c3 b/test/unit7/regression/any.c3 new file mode 100644 index 000000000..f3324bec7 --- /dev/null +++ b/test/unit7/regression/any.c3 @@ -0,0 +1,25 @@ +module any_tests @test; + +fn void any_compare() +{ + int x; + any a = &x; + any b = &x; + assert(a == b); + assert(a == a); +} + +def AnyAlias = any; + +fn void test_aliasing() +{ + int x; + AnyAlias z = &x; + switch (z) + { + case int: + assert(true); + default: + assert(false); + } +} \ No newline at end of file diff --git a/test/unit7/regression/bitstruct_ops.c3 b/test/unit7/regression/bitstruct_ops.c3 new file mode 100644 index 000000000..d74c853e2 --- /dev/null +++ b/test/unit7/regression/bitstruct_ops.c3 @@ -0,0 +1,44 @@ +module bitstruct_ops; +import std::io; + +bitstruct Foo : int +{ + bool a : 0; + bool b : 1; +} + +bitstruct Bar : char[13] +{ + bool z : 0; + bool w : 1; + bool gh : 25; +} + +fn void test_bitops() @test +{ + Foo f1 = { true, true }; + Foo f2 = { true, false }; + + Foo f3 = f1 & f2; + assert(f3.a == true); + assert(f3.b == false); + + Foo f4 = (f1 | ~f2) ^ f3; + assert(f4.a == false && f4.b == true); + Foo f5 = (Foo) { true, false } | (Foo) { false, true }; + assert(f5.a == true && f5.b == true); + + f5 &= f2; + assert(f5.a == true && f5.b == false); + + Bar b1 = { true, true, true }; + Bar b2 = { true, false, false }; + + Bar b3 = b1 & b2; + assert(b3.z == true && b3.w == false && b3.gh == false); + b3 = ~b3; + assert(b3.z == false && b3.w == true && b3.gh == true); + b3 ^= (Bar) { true, true, false }; + assert(b3.z == true && b3.w == false && b3.gh == true); +} + diff --git a/test/unit7/regression/bitstruct_ops2.c3 b/test/unit7/regression/bitstruct_ops2.c3 new file mode 100644 index 000000000..a2a374a3a --- /dev/null +++ b/test/unit7/regression/bitstruct_ops2.c3 @@ -0,0 +1,65 @@ +module bitstruct_ops_bool; +import std::io; + +bitstruct Foo : int +{ + bool a; + bool b; +} + +bitstruct Bar : char[13] +{ + bool z; + bool w; + bool gh; +} + +fn void test_bitops() @test +{ + Foo f1 = { true, true }; + Foo f2 = { true, false }; + + Foo f3 = f1 & f2; + assert(f3.a == true); + assert(f3.b == false); + + Foo f4 = (f1 | ~f2) ^ f3; + assert(f4.a == false && f4.b == true); + Foo f5 = (Foo) { true, false } | (Foo) { false, true }; + assert(f5.a == true && f5.b == true); + + f5 &= f2; + assert(f5.a == true && f5.b == false); + + Bar b1 = { true, true, true }; + Bar b2 = { true, false, false }; + + Bar b3 = b1 & b2; + assert(b3.z == true && b3.w == false && b3.gh == false); + b3 = ~b3; + assert(b3.z == false && b3.w == true && b3.gh == true); + b3 ^= (Bar) { true, true, false }; + assert(b3.z == true && b3.w == false && b3.gh == true); +} + +fn void test_bitops_const() @test +{ + const Foo F1 = { true, true }; + const Foo F2 = { true, false }; + + const Foo F3 = F1 & F2; + assert(F3.a == true); + assert(F3.b == false); + + const Foo F4 = (F1 | ~F2) ^ F3; + assert(F4.a == false && F4.b == true); + const Foo F5 = (Foo) { true, false } | (Foo) { false, true }; + assert(F5.a == true && F5.b == true); + + const Bar B1 = { true, true, true }; + const Bar B2 = { true, false, false }; + + const Bar B3 = B1 & B2; + assert(B3.z == true && B3.w == false && B3.gh == false); +} + diff --git a/test/unit7/regression/bitstruct_ops3.c3 b/test/unit7/regression/bitstruct_ops3.c3 new file mode 100644 index 000000000..ce58e9e8d --- /dev/null +++ b/test/unit7/regression/bitstruct_ops3.c3 @@ -0,0 +1,90 @@ +module bitstruct_ops_inc @test; +import std::io; + +struct Data { + bitstruct : ushort @overlap { + char a : 0..7; + } + bitstruct : ushort @overlap { + ushort ab : 0..15; + } +} + +bitstruct Foo : char[10] +{ + int a : 10..15; +} + +struct NewData { + char a; + ushort ab; +} + +fn void test_inc() +{ + NewData newdata; + newdata.a = 0x55; + newdata.ab = 0xAABB; + NewData newdata2 = newdata; + + newdata.a += 1; + newdata.ab += 1; + newdata2.a++; + newdata2.ab++; + assert(newdata.a == newdata2.a, "Structs: Expected %x, found %x", newdata.a, newdata2.a); + assert(newdata.ab == newdata2.ab, "Structs: Expected %x, found %x", newdata.ab, newdata2.ab); + + Data data; + data.a = 0x55; + data.ab = 0xAABB; + Data data2 = data; + + data.a += 1; + data.ab += 1; + data2.a++; + data2.ab++; + assert(data.a == data2.a, "BitStructs: Expected %x, found %x", data.a, data2.a); + assert(data.ab == data2.ab, "BitStructs: Expected %x, found %x", data.ab, data2.ab); + + data.a -= 1; + data.ab -= 1; + data2.a--; + data2.ab--; + assert(data.a == data2.a, "BitStructs: Expected %x, found %x", data.a, data2.a); + assert(data.ab == data2.ab, "BitStructs: Expected %x, found %x", data.ab, data2.ab); + + char a = data.a; + assert(a == data.a++); + assert(a != data.a); + assert(a == --data.a); + +} + + +fn void test_inc_array() +{ + Foo x; + x.a = 10; + assert(x.a == 10); + assert(x.a++ == 10); + assert(x.a == 11, "Value was %d", x.a); + assert(--x.a == 10); +} + +bitstruct Flags : int +{ + bool flag1; + bool flag2; +} + +fn void negate() +{ + Flags flags; + flags = ~flags; + assert(~0 == (int)flags); + flags = ~(Flags){}; + assert(3 == (int)flags); + const Flags FLAGS = {.flag1 }; + flags = ~FLAGS; + assert(2 == (int)flags); +} \ No newline at end of file diff --git a/test/unit7/regression/cast_slice_to_arr.c3 b/test/unit7/regression/cast_slice_to_arr.c3 new file mode 100644 index 000000000..9e6638a71 --- /dev/null +++ b/test/unit7/regression/cast_slice_to_arr.c3 @@ -0,0 +1,17 @@ +module slice_to_arr @test; + +fn void to_arr() +{ + int[] x = { 1, 2, 3, 4, 5 }; + int z = 2; + int[2] y = x[z:2]; + assert(y == {3, 4}); +} + +fn void to_vec() +{ + int[] x = { 1, 2, 3, 4, 5 }; + int z = 2; + int[<2>] y = x[z:2]; + assert(y == { 3, 4 }); +} \ No newline at end of file diff --git a/test/unit7/regression/castable_assignable.c3 b/test/unit7/regression/castable_assignable.c3 new file mode 100644 index 000000000..9254778d4 --- /dev/null +++ b/test/unit7/regression/castable_assignable.c3 @@ -0,0 +1,24 @@ +module castable @test; + +fn void assignable() +{ + assert($assignable(12.0, int) == false); + assert($assignable(12, int)); + assert(!$assignable("12", int)); + assert($assignable("12", String)); + assert($assignable("12", char*)); + assert($assignable("12", char[?])); + assert($assignable("12", char[2])); + assert($assignable("12", char[3])); +} + +fn void castable() +{ + assert($defined((int)12.0)); + assert($defined((int)12)); + assert(!$defined((int)"12")); + assert($defined((String)"12")); + assert($defined((char*)"12")); + assert($defined((char[2])"12")); + assert($defined((char[3])"12")); +} \ No newline at end of file diff --git a/test/unit7/regression/catch_err.c3 b/test/unit7/regression/catch_err.c3 new file mode 100644 index 000000000..171abf6f4 --- /dev/null +++ b/test/unit7/regression/catch_err.c3 @@ -0,0 +1,21 @@ +module catch_err @test; + +fn void test() +{ + anyfault a; + int! z = fn int!(anyfault* a) { + const ABC = 4; + int! x = SearchResult.MISSING?; + defer (catch err) *a = err; + return x; + }(&a); + assert(a == SearchResult.MISSING); + anyfault y; + z = fn int!(anyfault* y) { + const ABC = 4; + int! x = 1; + defer (catch err) *y = err; + return x; + }(&y); + assert(!y); +} \ No newline at end of file diff --git a/test/unit7/regression/copysign.c3 b/test/unit7/regression/copysign.c3 new file mode 100644 index 000000000..1c0fa5d82 --- /dev/null +++ b/test/unit7/regression/copysign.c3 @@ -0,0 +1,15 @@ +module copysign @test; +import std::math; +fn void copysign_float() +{ + float a = 3; + float b = -4; + float c = math::copysign(a, b); + assert(c == -3); + float d = math::copysign(a, 3); + assert(d == 3); + assert(math::copysign(a, -3) == -3); + float e = math::copysign(3, a); + assert(e == 3); + assert(math::copysign(3, b) == -3); +} \ No newline at end of file diff --git a/test/unit7/regression/ct_slice.c3 b/test/unit7/regression/ct_slice.c3 new file mode 100644 index 000000000..3c38badbc --- /dev/null +++ b/test/unit7/regression/ct_slice.c3 @@ -0,0 +1,38 @@ +module ct_slice @test; + +fn void slice_bytes() +{ + char[4] $a = x'aabbccdd'; + var $b = $a[1..2]; + char[2] y = $b; + assert($b == (char[]){187, 204}); + assert(y == {187, 204 }); +} + +fn void slice_string() +{ + String $a = "abcd"; + test::eq($a, "abcd"); + var $c = $a[1:0]; + String c = $c; + assert($c == ""); + assert(c == ""); + var $d = $a[1:2]; + String d = $d; + assert($d == "bc"); + assert(d == "bc"); + assert($a[..] == "abcd"); +} + +fn void slice_untyped() +{ + var $v = { 1, "hello", 3 }; + int[1] v = $v[0:1]; + assert(v == { 1 }); + int[1] v2 = $v[2:1]; + assert(v2 == { 3 }); + String[1] v3 = $v[1:1]; + assert(v3 == { "hello" }); + int[] v4 = $v[0:1]; + assert(v4 == { 1 }); +} diff --git a/test/unit7/regression/distinct_inline.c3 b/test/unit7/regression/distinct_inline.c3 new file mode 100644 index 000000000..85d5d0332 --- /dev/null +++ b/test/unit7/regression/distinct_inline.c3 @@ -0,0 +1,49 @@ +module distinct_inline @test; + +distinct Foo = inline int; +distinct Bar = inline Foo; +distinct Baz = inline Foo; +distinct Abc = inline Baz; +distinct Def = inline Abc; +distinct Other = inline int; +distinct Other2 = inline Other; +fn void test_binary() +{ + assert($typeof((Foo)1 + 1).typeid == Foo.typeid); + assert($typeof((Foo)1 + (Bar)1).typeid == Foo.typeid); + assert($typeof((Baz)1 + (Bar)1).typeid == Foo.typeid); + assert($typeof((Def)1 + (Bar)1).typeid == Foo.typeid); + assert($typeof((Other2)1 + (Def)1).typeid == int.typeid); +} + +distinct DistinctInt = inline int; +distinct DistinctUInt = inline uint; + +fn void test_comparison() +{ + char v_char; + ichar v_ichar; + short v_short; + short v_ushort; + int v_int; + uint v_uint; + long v_long; + ulong v_ulong; + assert((DistinctInt)0 == v_ichar); + assert((DistinctInt)0 == v_char); + assert((DistinctInt)0 == v_short); + assert((DistinctInt)0 == v_ushort); + assert((DistinctInt)0 == v_int); + assert((DistinctInt)0 == v_uint); + assert((DistinctInt)0 == v_long); + assert((DistinctInt)0 == v_ulong); + + assert((DistinctUInt)0 == v_ichar); + assert((DistinctUInt)0 == v_char); + assert((DistinctUInt)0 == v_short); + assert((DistinctUInt)0 == v_ushort); + assert((DistinctUInt)0 == v_int); + assert((DistinctUInt)0 == v_uint); + assert((DistinctUInt)0 == v_long); + assert((DistinctUInt)0 == v_ulong); +} \ No newline at end of file diff --git a/test/unit7/regression/faults.c3 b/test/unit7/regression/faults.c3 new file mode 100644 index 000000000..e4201162f --- /dev/null +++ b/test/unit7/regression/faults.c3 @@ -0,0 +1,21 @@ +module faults @test; + +fault Foo +{ + ABC, + CDE +} + +fn void ordinals() +{ + Foo z = {}; + assert(z.ordinal == 0); + $assert Foo.ABC.ordinal == 1; + $assert Foo.CDE.ordinal == 2; + $assert (Foo){}.ordinal == 0; + Foo x = Foo.CDE; + assert(x.ordinal == 2); + x = Foo.ABC; + assert(x.ordinal == 1); +} + diff --git a/test/unit7/regression/file_line_func_module_builtins.c3 b/test/unit7/regression/file_line_func_module_builtins.c3 new file mode 100644 index 000000000..dfe47b802 --- /dev/null +++ b/test/unit7/regression/file_line_func_module_builtins.c3 @@ -0,0 +1,22 @@ +module test::module_a; +import std::io; + +macro line() => $$LINE; +macro func() => $$FUNC; +macro mod() => $$MODULE; + +macro line_indirect() => line(); +macro func_indirect() => func(); +macro mod_indirect() => mod(); + +module test @test; + +fn void test_builtins() +{ + assert(module_a::line() == module_a::line_indirect()); + assert(module_a::line() == $$LINE); + assert(module_a::func() == module_a::func_indirect()); + assert(module_a::func() == $$FUNC); + assert(module_a::mod() == module_a::mod_indirect()); + assert(module_a::mod() == $$MODULE); +} \ No newline at end of file diff --git a/test/unit7/regression/gather_scatter.c3 b/test/unit7/regression/gather_scatter.c3 new file mode 100644 index 000000000..763a2650b --- /dev/null +++ b/test/unit7/regression/gather_scatter.c3 @@ -0,0 +1,16 @@ +module gather_scatter; + +fn void test_simple() @test +{ + int[5] a = { 1, 2, 3, 4, 5 }; + void*[<2>] x = { &a[0], &a[4] }; + int*[<2>] y = x; + int[<2>] result = mem::@gather_aligned(y, (bool[<2>]) { true, false }, (int[<2>]){ 10, 20 }, 4); + assert(result == { 1, 20}); + result = mem::gather(y, (bool[<2>]) { true, false }, (int[<2>]){ 10, 20 }); + assert(result == { 1, 20}); + mem::@scatter_aligned(y, (int[<2>]){ 66, 77 }, (bool[<2>]) { false, true } , 4); + assert(a == (int[5]){ 1, 2, 3, 4, 77}); + mem::scatter(y, (int[<2>]){ 88, 99 }, (bool[<2>]) { true, false }); + assert(a == (int[5]){ 88, 2, 3, 4, 77}); +} diff --git a/test/unit7/regression/inc_dec.c3 b/test/unit7/regression/inc_dec.c3 new file mode 100644 index 000000000..3118df781 --- /dev/null +++ b/test/unit7/regression/inc_dec.c3 @@ -0,0 +1,34 @@ +fn void vector_inc_dec() @test +{ + int[<3>] x; + int[<3>] y; + int z = ++x[0]; + int zz = y[0]++; + x[1]++; + ++y[2]; + int[<3>] g = x--; + assert(x == { 0, 0, -1 }); + assert(y == { 1, 0, 1 }); + assert(z == 0 && zz == 1); + assert(g == { 1, 1, 0 }); +} + +fn void int_inc_dec() @test +{ + int x; + assert(x++ == 0); + assert(x == 1); + assert(++x == 2); + assert(x-- == 2); + assert(--x == 0); +} + +fn void float_inc_dec() @test +{ + double x; + assert(x++ == 0); + assert(x == 1.0); + assert(++x == 2.0); + assert(x-- == 2.0); + assert(--x == 0); +} diff --git a/test/unit7/regression/int128.c3 b/test/unit7/regression/int128.c3 new file mode 100644 index 000000000..12b063f9d --- /dev/null +++ b/test/unit7/regression/int128.c3 @@ -0,0 +1,21 @@ +module int128_test; + +fn void check(uint128 a, uint128 b) +{ + uint128 div = a / b; + uint128 mod = a % b; + assert(div * b + mod == a); +} + +fn void test_big() @test +{ + uint128 a = 12345678901234567890012u128; + uint128 b = 1234567890123456789001u128; + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + check(a + i, b + j); + } + } +} diff --git a/test/unit7/regression/int_min.c3 b/test/unit7/regression/int_min.c3 new file mode 100644 index 000000000..61b3e0722 --- /dev/null +++ b/test/unit7/regression/int_min.c3 @@ -0,0 +1,9 @@ +fn void int_min() @test +{ + assert(int.min == -2147483648); + assert((float)int.min == -2147483648.0f); + assert(short.min == -32768); + assert((float)short.min == -32768.0f); + assert(ichar.min == -128); + assert((float)ichar.min == -128.0f); +} diff --git a/test/unit7/regression/liveness_any.c3 b/test/unit7/regression/liveness_any.c3 new file mode 100644 index 000000000..bd271ca61 --- /dev/null +++ b/test/unit7/regression/liveness_any.c3 @@ -0,0 +1,22 @@ +module liveness; + +interface TestProto +{ + fn void tesT(); +} + +fn void Test.tesT(&self) @dynamic +{} + +struct Test (TestProto) +{ + void* abc; +} + +fn void reflect_test() @test +{ + TestProto b = mem::alloc(Test); + b.tesT(); + defer free(b); + +} \ No newline at end of file diff --git a/test/unit7/regression/lvalue_handling.c3 b/test/unit7/regression/lvalue_handling.c3 new file mode 100644 index 000000000..eb60cfade --- /dev/null +++ b/test/unit7/regression/lvalue_handling.c3 @@ -0,0 +1,21 @@ +module lvalue_handling; +import std; +struct Foo +{ + int a; +} +def IntList = List{Foo}; +fn void subscript_overload() @test +{ + IntList x; + defer x.free(); + + x.push({ 3 }); + int* a = &x[0].a; + assert(*a == 3); + assert(x[0].a == 3); + *a = 4; + assert(x[0].a == 4); + x[0].a = 5; + assert(x[0].a == 5); +} \ No newline at end of file diff --git a/test/unit7/regression/masked_load_store.c3 b/test/unit7/regression/masked_load_store.c3 new file mode 100644 index 000000000..3ae822d50 --- /dev/null +++ b/test/unit7/regression/masked_load_store.c3 @@ -0,0 +1,28 @@ +import std; +fn void masked_load() @test +{ + long[<4>] val = { 1, 3, 5, 8 }; + long[<4>] res = mem::masked_load(&val, { true, false, true, false }, (long[<4>]){ 100, 200, 300, 400 }); + assert(res == { 1, 200, 5, 400 }); +} + +fn void masked_load_aligned() @test +{ + long[<4>] val = { 1, 3, 5, 8 }; + long[<4>] res = mem::@masked_load_aligned(&val, { true, false, true, false }, (long[<4>]) { 100, 200, 300, 400 }, 8); + assert(res == { 1, 200, 5, 400 }); +} + +fn void masked_store() @test +{ + long[<4>] val = { 1, 3, 5, 8 }; + mem::masked_store(&val, (long[<4>]){11, 22, 33, 44}, { true, false, false, true }); + assert(val == { 11, 3, 5, 44 }); +} + +fn void masked_store_aligned() @test +{ + long[<4>] val = { 1, 3, 5, 8 }; + mem::@masked_store_aligned(&val, (long[<4>]){11, 22, 33, 44}, { true, false, false, true }, 8); + assert(val == { 11, 3, 5, 44 }); +} diff --git a/test/unit7/regression/methodsof.c3 b/test/unit7/regression/methodsof.c3 new file mode 100644 index 000000000..ecf30caa4 --- /dev/null +++ b/test/unit7/regression/methodsof.c3 @@ -0,0 +1,80 @@ +module methodsof; + +interface IBar +{ + fn void fnA(); + fn void fnB(); +} +struct Bar (IBar) +{ + int a; +} +fn void Bar.fnB(&self) @dynamic {} +fn void Bar.fnA(&self) @dynamic {} + +struct Foo +{ + int i; + bool b; +} + +fn void Foo.foo(&self) {} +fn int Foo.bar(&self, int x) { return x * self.i; } +fn bool Foo.xyz(&self) { return self.b; } + +struct NoMethods +{ + int a; +} + +bitstruct BazBits : char +{ + int a : 0..2; + int b : 4..6; + bool c : 7; +} + +fn void BazBits.fn1(&self) {} +fn void BazBits.fn2(&self) {} + +union AUnion { + int y; + double z; +} + +fn void AUnion.a(&self) {} +fn void AUnion.b(&self) {} + + +module methodsof @test; + +import std::io; + +fn void methodsof() +{ + assert(Foo.methodsof.len == 3); + assert(Bar.methodsof.len == 2); + assert(NoMethods.methodsof.len == 0); + + assert(Foo.methodsof[0] == "foo"); + assert(Foo.methodsof[1] == "bar"); + assert(Foo.methodsof[2] == "xyz"); + assert(Bar.methodsof[0] == "fnB"); + assert(Bar.methodsof[1] == "fnA"); + + assert(BazBits.methodsof[0] == "fn1"); + assert(BazBits.methodsof[1] == "fn2"); + + assert(AUnion.methodsof[0] == "a"); + assert(AUnion.methodsof[1] == "b"); + + Foo foo = { .i = 4, .b = true }; + assert(Foo.$eval(Foo.methodsof[2])(&foo) == true); // Foo.xyz + assert(Foo.$eval(Foo.methodsof[1])(&foo, 2) == 8); // Foo.bar + assert(Foo.$eval(Foo.methodsof[2])(&foo) == true); // Foo.xyz + assert(Foo.$eval(Foo.methodsof[1])(&foo, 2) == 8); // Foo.bar + + Foo foo2 = { .i = 2, .b = false }; + assert(foo2.$eval(Foo.methodsof[2])() == false); // Foo.xyz + assert(foo2.$eval(Foo.methodsof[1])(10) == 20); // Foo.bar +} diff --git a/test/unit7/regression/pointer_diff.c3 b/test/unit7/regression/pointer_diff.c3 new file mode 100644 index 000000000..cf61f99db --- /dev/null +++ b/test/unit7/regression/pointer_diff.c3 @@ -0,0 +1,20 @@ +import std; +struct Foo +{ + int a,b,c,d; +} + +fn void pointer_diff() @test +{ + Foo* foo; + isz offset = &foo[1] - &foo[0]; + assert(offset == 1); +} + +fn void pointer_add() @test +{ + Foo* foo; + Foo* bar = foo + 2; + isz offset = bar - foo; + assert(offset == 2); +} \ No newline at end of file diff --git a/test/unit7/regression/pointer_non_decay.c3 b/test/unit7/regression/pointer_non_decay.c3 new file mode 100644 index 000000000..de7c1cd04 --- /dev/null +++ b/test/unit7/regression/pointer_non_decay.c3 @@ -0,0 +1,19 @@ +module test @test; + +fn void pointer_non_decay() +{ + int[6] x; + int[3]* y = (int[3]*)&x; + int* z = y; + int[] sub = y; + int[3] y1 = y[1]; + int z1 = z[1]; + int* xx = y + 1; + int[3]* yy = (int[3]*)(xx); + int* zz = yy - 1; + assert(y == z); + assert(z == zz); + assert(&(*y)[1] == &xx[-2]); + x[1] = 123; + assert(x[1] == z[1]); +} \ No newline at end of file diff --git a/test/unit7/regression/select.c3 b/test/unit7/regression/select.c3 new file mode 100644 index 000000000..fcac2222d --- /dev/null +++ b/test/unit7/regression/select.c3 @@ -0,0 +1,10 @@ +import std; +fn long[<4>] process(long[<4>] a, long[<4>] b) @noinline +{ + return math::select({ true, true, false, true }, a, b); +} +fn void test_select() @test +{ + long[<4>] res = process({ 4, 5, 6, 7 }, { 100, 200, 300, 400 }); + assert(res == { 4, 5, 300, 7}); +} diff --git a/test/unit7/regression/signed_unsigned_compare.c3 b/test/unit7/regression/signed_unsigned_compare.c3 new file mode 100644 index 000000000..85f73a905 --- /dev/null +++ b/test/unit7/regression/signed_unsigned_compare.c3 @@ -0,0 +1,157 @@ +module signed_unsigned_comparisons @test; + +fn void test_signed() +{ + int a = 0; + int b = 1; + + bool ab_gt = a > b; + bool ab_ge = a >= b; + bool ab_le = a <= b; + bool ab_lt = a < b; + bool ab_ne = a != b; + bool ab_eq = a == b; + +} + +fn void test_unsigned() +{ + uint a = 0; + uint b = 1; + + bool ab_gt = a > b; + bool ab_ge = a >= b; + bool ab_le = a <= b; + bool ab_lt = a < b; + bool ab_ne = a != b; + bool ab_eq = a == b; + +} + +fn void test_signedunsigned() +{ + ichar a = 0 - 1; + char b = (char)(a); + + assert(!(a > b)); + assert(!(a >= b)); + assert(a < b); + assert(a <= b); + assert(a != b); + assert(!(a == b)); + + a = 1; + b = 1; + + assert(!(a > b)); + assert(a >= b); + assert(!(a < b)); + assert(a <= b); + assert(!(a != b)); + assert(a == b); + + a = 1; + b = 4; + + assert(!(a > b)); + assert(!(a >= b)); + assert(a < b); + assert(a <= b); + assert(a != b); + assert(!(a == b)); + + a = 4; + b = 1; + + assert(a > b); + assert(a >= b); + assert(!(a < b)); + assert(!(a <= b)); + assert(a != b); + assert(!(a == b)); + + a = 4; + b = 129; + + assert(!(a > b)); + assert(!(a >= b)); + assert(a < b); + assert(a <= b); + assert(a != b); + assert(!(a == b)); + + a = 0 - 4; + b = 129; + + assert(!(a > b)); + assert(!(a >= b)); + assert(a < b); + assert(a <= b); + assert(a != b); + assert(!(a == b)); + +} + +fn void test_unsignedsigned() +{ + int b = -1; + uint a = (uint)(b); + + assert(a > b); + assert(a >= b); + assert(!(a < b)); + assert(!(a <= b)); + assert(a != b); + assert(!(a == b)); + + a = 1; + b = 1; + + assert(!(a > b)); + assert(a >= b); + assert(!(a < b)); + assert(a <= b); + assert(!(a != b)); + assert(a == b); + + a = 4; + b = 1; + + assert(a > b); + assert(a >= b); + assert(!(a < b)); + assert(!(a <= b)); + assert(a != b); + assert(!(a == b)); + + a = 1; + b = 4; + + assert(!(a > b)); + assert(!(a >= b)); + assert(a < b); + assert(a <= b); + assert(a != b); + assert(!(a == b)); + + a = 0x8000_0001; + b = 4; + + assert(a > b); + assert(a >= b); + assert(!(a < b)); + assert(!(a <= b)); + assert(a != b); + assert(!(a == b)); + + b = 0 - 4; + a = 0x8000_0001; + + assert(a > b); + assert(a >= b); + assert(!(a < b)); + assert(!(a <= b)); + assert(a != b); + assert(!(a == b)); + +} diff --git a/test/unit7/regression/slice_assign.c3 b/test/unit7/regression/slice_assign.c3 new file mode 100644 index 000000000..f07970f5a --- /dev/null +++ b/test/unit7/regression/slice_assign.c3 @@ -0,0 +1,9 @@ +module slice_assign @test; + +fn void assign_slice() +{ + int[8] a; + a[2..3] = { 1, 2 }; + a[5..7] = 5; + assert(a == (int[8]){ 0, 0, 1, 2, 0, 5, 5, 5}); +} \ No newline at end of file diff --git a/test/unit7/regression/struct_alignment.c3 b/test/unit7/regression/struct_alignment.c3 new file mode 100644 index 000000000..e814fff65 --- /dev/null +++ b/test/unit7/regression/struct_alignment.c3 @@ -0,0 +1,18 @@ +module struct_alignment @test; + +struct Test @align(16) { void* foo; } + +struct Test2 +{ + Test test; + uint a; +} + +fn void nested_struct() +{ + Test2* array; + assert((uptr)&array[1] - (uptr)array == 32); + assert((uptr)&array[1] - (uptr)array == Test2.sizeof); + assert(Test2.sizeof == 32); + assert(Test.sizeof == 16); +} \ No newline at end of file diff --git a/test/unit7/regression/subscripting.c3 b/test/unit7/regression/subscripting.c3 new file mode 100644 index 000000000..54e3a5a89 --- /dev/null +++ b/test/unit7/regression/subscripting.c3 @@ -0,0 +1,15 @@ +module subscripting_tests @test; + +fn void subscript_ct() +{ + $if int.nameof[0] == 'i': + assert(true); + $else + assert(false); + $endif + $if int.nameof[^1] == 't': + assert(true); + $else + assert(false); + $endif +} \ No newline at end of file diff --git a/test/unit7/regression/subtype.c3 b/test/unit7/regression/subtype.c3 new file mode 100644 index 000000000..a73c1ac17 --- /dev/null +++ b/test/unit7/regression/subtype.c3 @@ -0,0 +1,91 @@ +module switch_subtype @test; + +struct Foo +{ + inline Baz g; + int a; +} +struct Bar +{ + inline Foo f; +} +struct Baz +{ + int z; +} +fn void switch_subtyping() +{ + Foo f = {}; + f.z = 123; + any x = &f; + switch (x) + { + case int: + assert(false); + case Baz: + assert(true); + default: + assert(false); + } + switch (x) + { + case int: + assert(false); + case Foo: + assert(true); + default: + assert(false); + } + switch (x) + { + case int: + assert(false); + case Bar: + assert(false); + default: + assert(true); + } + switch (Bar.typeid) + { + case int: + assert(false); + case Baz: + assert(true); + case Foo: + assert(false); + case Bar: + assert(false); + default: + assert(false); + } + switch (Bar.typeid) + { + case int: + assert(false); + case Foo: + assert(true); + case Bar: + assert(false); + default: + assert(false); + } + $switch (Bar.typeid) + $case Foo: + assert(true); + $case Bar: + assert(false); + $default: + assert(false); + $endswitch +} + +fn void is_subtype() +{ + $assert(types::is_subtype_of(Bar, Baz)); + $assert(!types::is_subtype_of(Baz, Bar)); + typeid baz = Baz.typeid; + typeid bar = Bar.typeid; + assert(bar.is_subtype_of(baz)); + assert(!bar.is_subtype_of(double.typeid)); + assert(!baz.is_subtype_of(bar)); +} diff --git a/test/unit7/regression/swizzle.c3 b/test/unit7/regression/swizzle.c3 new file mode 100644 index 000000000..f1c088c48 --- /dev/null +++ b/test/unit7/regression/swizzle.c3 @@ -0,0 +1,18 @@ +module swizzletest @test; + +fn void test_swizzle() +{ + int[<4>] a = { 1, 2, 3, 4 }; + int[<4>] b = { 100, 1000, 10000, 100000 }; + assert($$swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 }); + assert($$swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 }); +} + +fn void swizzle_builtin() +{ + int[<4>] abc = { 1, 2, 3, 4 }; + assert(abc.rb == { 1, 3 }); + assert(abc.xxww == { 1, 1, 4, 4 }); + abc = abc.abgr; + assert(abc == { 4, 3, 2, 1 }); +} diff --git a/test/unit7/regression/ternary.c3 b/test/unit7/regression/ternary.c3 new file mode 100644 index 000000000..97254966a --- /dev/null +++ b/test/unit7/regression/ternary.c3 @@ -0,0 +1,11 @@ +module test @test; + +fn void const_ternary() +{ + int foo = 1; + const int FOO = 1; + assert((foo ?: 2) == 1); + assert((FOO ?: 2) == 1); + int bar = 2; + assert((FOO ?: bar) == 1); +} diff --git a/test/unit7/regression/unwrapping.c3 b/test/unit7/regression/unwrapping.c3 new file mode 100644 index 000000000..f68fc5452 --- /dev/null +++ b/test/unit7/regression/unwrapping.c3 @@ -0,0 +1,19 @@ +module unwrapping; + +fn bool! get_bool() +{ + return true; +} + +fn void bool_chain_unwrap() @test +{ + bool b; + if (try v = get_bool() && b) + { + assert(v == true); + } + if (try v = get_bool() && v) + { + assert(v == true); + } +} \ No newline at end of file diff --git a/test/unit7/regression/vecpointer.c3 b/test/unit7/regression/vecpointer.c3 new file mode 100644 index 000000000..1499dd525 --- /dev/null +++ b/test/unit7/regression/vecpointer.c3 @@ -0,0 +1,35 @@ +module vecpointer @test; + +fn void pointer_npot2_size() +{ + int[<9>][3] a; + assert((usz)&a[1] - (usz)&a[0] == 64); +} + +fn void pointer_add_sub_diff() +{ + int[5] a; + void*[<2>] x = { &a[0], &a[4] }; + int*[<2>] y = x; + assert(x[0] == y[0] && x[1] == y[1]); + int*[2] y2; + y2 = y; + assert(y2[0] == x[0] && y2[1] == x[1]); + y = { null, null }; + assert(y[0] == null && y[0] == null); + y = y2; + assert(x[0] == y[0] && x[1] == y[1]); + int[<2>] z = { 1, -1 }; + y = y + z; + assert(y[0] == &a[1] && y[1] == &a[3]); + y = y - z; + assert(y[0] == &a[0] && y[1] == &a[4]); + int*[<2>] yy = { &a[1], &a[2] }; + isz[<2>] w; + w = y - yy; + assert(w == { -1, 2 }); + int*[<2>] zz = y - (y - yy); + assert(zz[0] == &a[1] && zz[1] == &a[2]); + int[?]*[<2>] g = (int[2]*[<2>]) { null, null }; + int[?]*[] g2 = (int[2]*[<2>]) { null, null }; +} \ No newline at end of file diff --git a/test/unit7/regression/vector_conversion.c3 b/test/unit7/regression/vector_conversion.c3 new file mode 100644 index 000000000..5f9589b0e --- /dev/null +++ b/test/unit7/regression/vector_conversion.c3 @@ -0,0 +1,31 @@ +module test @test; + +fn void vector_array_inferred() +{ + int[<2>] x = { 4, 7 }; + int[2] y = x; + int[?] y1 = y; + int[?] y2 = x; + int[] z = x; + int[] w = y; + double[<2>] ww = x; + assert((int[<2>])y == { 4, 7}); + assert((int[<2>])y1 == { 4, 7 }); + assert((int[<2>])y2 == { 4, 7 }); + assert(z == { 4, 7 }); + assert(w == { 4, 7 }); +} + +fn void vector_convert_slice() +{ + int[<2>] x = { 1, 2 }; + int[2] y = { 4, 4 }; + x *= y[:2]; + assert(x == { 4, 8 }); +} +fn void vector_convert_bool() +{ + bool[<2>] a = { true, false }; + assert({ -1, 0 } == (int[<2>])a); + assert({ 1.0, 0 } == (float[<2>])a); +} \ No newline at end of file diff --git a/test/unit7/regression/vector_method_reduce.c3 b/test/unit7/regression/vector_method_reduce.c3 new file mode 100644 index 000000000..a0f5a539d --- /dev/null +++ b/test/unit7/regression/vector_method_reduce.c3 @@ -0,0 +1,18 @@ +import std::math; + +fn void vector_method_reduce() @test +{ + float[<3>] x = { 1, 2.0, 4.0 }; + int[] y = { -23, 1, 4 }; + assert(y.sum() == -18); + assert(y.product() == -92); + assert(y.max() == 4); + assert(y.and() == 0); + assert(y.xor() == -20); + assert(y.min() == -23); + assert(y.or() == -19); + assert(x.sum(1.2) - 8.2 < 0.000001); + assert(x.product(1.2) - 9.6 < 0.000001); + assert(x.min() == 1.0); + assert(x.max() == 4.0); +} \ No newline at end of file diff --git a/test/unit7/regression/vector_ops.c3 b/test/unit7/regression/vector_ops.c3 new file mode 100644 index 000000000..59aa1133f --- /dev/null +++ b/test/unit7/regression/vector_ops.c3 @@ -0,0 +1,114 @@ +import std::io; + +fn void test_int_mod() @test +{ + int[<2>] y = { 10, 99 }; + int[<2>] z = { 3, 5 }; + assert(y % z == { 1, 4 }); + assert(y / z == { 3, 19 }); + assert((int[<2>]){ 10, 99 } % { 3, 5 } == { 1, 4 }); + assert((int[<2>]){ 10, 99 } / { 3, 5 } == { 3, 19 }); +} + +fn void test_conv() @test +{ + float[<4>] y = { 1, 2, 3, 4 }; + float[<4>] z = { 0, 2, 2, -100 }; + int[<4>] w = { -1, 2, 3, 4 }; + float[<4>] yy = w; + assert(yy == { -1.0, 2.0, 3.0, 4.0 }); + ulong[<4>] ww = w; + assert(ww == { (ulong)-1, 2, 3, 4 }); + int[<4>] g = (int[<4>])ww; + assert(g == w); + ww = (long[<4>])y; + assert(ww == { 1, 2, 3, 4 }); + bool[<2>] b = { true, false }; + int[<2>] gh = b; + assert(gh == { -1, 0 }); + var $k = (bool[<2>]){ true, false }; + var $gh = (int[<2>])$k; + $assert $gh[0] == -1; + var $gh2 = (char[<2>])$gh; + $assert $gh2[0] == 255; + b = (bool[<2>])gh; + assert(b == { true, false }); +} + +fn void testf() @test +{ + float[<4>] x = { 4, 0, -1, 33 }; + assert({ true, false, true, true} == (bool[<4>])x); + float[<4>] y = { 1, 2, 3, 4 }; + float[<4>] z = { 2, 2, 2, -100 }; + float[<4>] w = y + z; + assert(w == { 3, 4, 5, -96 }); + w = y * z; + assert(w == { 2, 4, 6, -400 }); + w = y / z; + assert(w == { 0.5, 1.0, 1.5, -0.04 }); + w = y - z; + assert(w == { -1, 0, 1, 104 }); + int[<4>] ww = $$veccomplt(y, z); + assert(ww == { -1, 0, 0, 0 }); + ww = $$veccomple(y, z); + assert(ww == { -1, -1, 0, 0 }); + ww = $$veccompgt(y, z); + assert(ww == { 0, 0, -1, -1 }); + ww = $$veccompge(y, z); + assert(ww == { 0, -1, -1, -1 }); + ww = $$veccompeq(y, z); + assert(ww == { 0, -1, 0, 0 }); + ww = $$veccompne(y, z); + assert(ww == { -1, 0, -1, -1 }); +} + +fn void testb() @test +{ + bool[<4>] y = { true, false, true, true }; + bool[<4>] z = { false, false, true, true }; + ichar[<4>] ww = $$veccomplt(y, z); + assert(ww == { 0, 0, 0, 0 }); + ww = $$veccomple(y, z); + assert(ww == { 0, -1, -1, -1 }); + ww = $$veccompgt(y, z); + assert(ww == { -1, 0, 0, 0 }); + ww = $$veccompge(y, z); + assert(ww == { -1, -1, -1, -1 }); + ww = $$veccompeq(y, z); + assert(ww == { 0, -1, -1, -1 }); + ww = $$veccompne(y, z); + assert(ww == { -1, 0, 0, 0 }); +} + +fn void testi() @test +{ + int[<4>] x = { 4, 0, -1, 33 }; + assert({ true, false, true, true} == (bool[<4>])x); + int[<4>] y = { 1, 2, 3, 4 }; + int[<4>] z = { 2, 2, 2, -100 }; + int[<4>] w = y + z; + assert(w == { 3, 4, 5, -96 }); + w = y * z; + assert(w == { 2, 4, 6, -400 }); + w = y / z; + assert(w == { 0, 1, 1, 0 }); + w = y - z; + assert(w == { -1, 0, 1, 104 }); + w = z >> y; + assert(w == { 1, 0, 0, -7}); + w = z << y; + assert(w == { 4, 8, 16, -1600 }); + w = $$veccompgt(z, y); + assert(w == { -1, 0, 0, 0}); + w = $$veccompge(z, y); + assert(w == { -1, -1, 0, 0 }); + w = $$veccomplt(z, y); + assert(w == { 0, 0, -1, -1 }); + w = $$veccomple(z, y); + assert(w == { 0, -1, -1, -1 }); + w = $$veccompeq(z, y); + assert(w == { 0, -1, 0, 0 }); + w = $$veccompne(z, y); + assert(w == { -1, 0, -1, -1 }); +} diff --git a/test/unit7/stdlib/atomic.c3 b/test/unit7/stdlib/atomic.c3 new file mode 100644 index 000000000..b7ba45bb1 --- /dev/null +++ b/test/unit7/stdlib/atomic.c3 @@ -0,0 +1,245 @@ +import std::thread; +import std::io; +import std::atomic; + +uint a; +float fa; + +fn void add() @test +{ + Thread[100] ts; + a = 0; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + thread::sleep_ms(5); + atomic::fetch_add(&a, 5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == ts.len * 10 * 5, "Threads returned %d, expected %d", a, ts.len * 10 * 5); +} + +fn void sub() @test +{ + Thread[100] ts; + a = ts.len * 10 * 5; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + thread::sleep_ms(5); + atomic::fetch_sub(&a, 5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == 0, "Threads returned %d, expected %d", a, 0); +} + +fn void div() @test +{ + Thread[8] ts; + a = 8 * 8 * 8 * 8 * 8 * 8 * 8 * 8 * 8; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + atomic::fetch_div(&a, 8); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == 8, "Threads returned %d, expected %d", a, 8); +} + +fn void max() @test +{ + Thread[100] ts; + a = 0; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + uint la = 0; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + thread::sleep_ms(5); + atomic::fetch_max(&a, la); + la++; + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == 5, "Threads returned %d, expected %d", a, 5); +} + +fn void min() @test +{ + Thread[100] ts; + a = 10; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + uint la = 5; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + thread::sleep_ms(5); + atomic::fetch_min(&a, la); + la--; + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == 0, "Threads returned %d, expected %d", a, 0); +} + +fn void fadd() @test +{ + Thread[100] ts; + fa = 0; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_add(&fa, 0.5f); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(fa == ts.len * 10 * 0.5, "Threads returned %f, expected %f", fa, ts.len * 10 * 0.5); +} + +fn void fsub() @test +{ + Thread[100] ts; + fa = ts.len * 10 * 0.5; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + thread::sleep_ms(5); + atomic::fetch_sub(&fa, 0.5f); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(fa == 0, "Threads returned %f, expected %f", fa, 0); +} \ No newline at end of file diff --git a/test/unit7/stdlib/atomic_types.c3 b/test/unit7/stdlib/atomic_types.c3 new file mode 100644 index 000000000..009ef8138 --- /dev/null +++ b/test/unit7/stdlib/atomic_types.c3 @@ -0,0 +1,156 @@ +import std::thread; +import std::io; +import std::atomic::types; + +def AtomicUint = Atomic{uint}; +def AtomicFloat = Atomic{float}; +AtomicUint a; +AtomicFloat fa; + +fn void add() @test +{ + Thread[100] ts; + a.store(0); + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + thread::sleep_ms(5); + a.add(5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a.load() == ts.len * 10 * 5, "Threads returned %d, expected %d", a.load(), ts.len * 10 * 5); +} + +fn void sub() @test +{ + Thread[100] ts; + a.store(ts.len * 10 * 5); + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + thread::sleep_ms(5); + a.sub(5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a.load() == 0, "Threads returned %d, expected %d", a.load(), 0); +} + +fn void fadd() @test +{ + Thread[100] ts; + fa.store(0); + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + thread::sleep_ms(5); + fa.add(0.5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(fa.load() == ts.len * 10 * 0.5, "Threads returned %f, expected %f", fa.load(), ts.len * 10 * 0.5); +} + +fn void fsub() @test +{ + Thread[100] ts; + fa.store(ts.len * 10 * 0.5); + foreach (&t : ts) + { + t.create(fn int(void* arg) { + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + thread::sleep_ms(5); + fa.sub(0.5); + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(fa.load() == 0, "Threads returned %f, expected %f", fa.load(), 0); +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/bitset.c3 b/test/unit7/stdlib/collections/bitset.c3 new file mode 100644 index 000000000..21d8adfea --- /dev/null +++ b/test/unit7/stdlib/collections/bitset.c3 @@ -0,0 +1,89 @@ +module bitset_test @test; +import std::collections::bitset; +import std::collections::growablebitset; +import std::collections::list; +import std::io; + +def List = List{usz}; + +def BitSet = BitSet{2048}; + +fn void set_get() +{ + BitSet bs; + assert(bs.cardinality() == 0); + + assert(!bs.get(0)); + bs.set(0); + assert(bs.get(0)); + assert(bs.cardinality() == 1); + + assert(!bs.get(2000)); + bs[2000] = true; + assert(bs.get(2000)); + assert(bs.cardinality() == 2); + + List found; + foreach (i, x : bs) + { + switch (i) + { + case 0: + case 2000: + assert(x); + found.push(i); + default: + assert(!x); + } + } + assert(found.array_view() == (usz[]){0, 2000}); + + bs.unset(0); + assert(!bs.get(0)); + bs[2000] = false; + assert(!bs.get(2000)); + assert(bs.cardinality() == 0); +} + +def GrowableBitSet = GrowableBitSet{char}; +fn void growable_set_get() +{ + GrowableBitSet bs; + bs.tinit(); + assert(bs.cardinality() == 0, "Invalid cardinality"); + + assert(!bs.get(0), "Get was true"); + bs.set(0); + assert(bs.get(0), "Get should be true"); + assert(bs.cardinality() == 1, "Cardinality should be 1"); + assert(bs.len() == 1, "Len should be 1"); + + assert(!bs.get(2000), "Get 2000 should be false"); + bs[2000] = true; + assert(bs.get(2000), "Get 2000 should be true"); + assert(bs.cardinality() == 2, "Cardinality should be 2"); + + assert(bs.data.len() == 251, "Len should be 251"); + assert(bs.len() == 2001, "Len should be 2001"); + + List found; + foreach (i, x : bs) + { + switch (i) + { + case 0: + case 2000: + assert(x); + found.push(i); + default: + assert(!x, "Should not get here"); + } + } + assert(found.array_view() == (usz[]){0, 2000}, "Array view should hold 2"); + + bs.unset(0); + assert(!bs.get(0), "Get should be false"); + bs[2000] = false; + assert(!bs.get(2000), "Get should be false"); + assert(bs.cardinality() == 0, "Cardinality should be 0"); +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/copy_map.c3 b/test/unit7/stdlib/collections/copy_map.c3 new file mode 100644 index 000000000..a85a77f60 --- /dev/null +++ b/test/unit7/stdlib/collections/copy_map.c3 @@ -0,0 +1,53 @@ +module test; +import std::io; +import std::collections::map; + +def IntMap = HashMap{String, int}; +fn void copy_map() @test +{ + TrackingAllocator alloc; + alloc.init(allocator::heap()); + defer alloc.free(); + assert(alloc.allocated() == 0); + mem::@scoped(&alloc) + { + IntMap x; + x.init(mem); + DString y; + y.append("hello"); + x.set(y.str_view(), 123); + y.append("bye"); + x.set(y.str_view(), 333); + y.clear(); + y.append("bye"); + x.set(y.str_view(), 444); + assert(x.get("hello")!! == 123); + assert(x.get("hellobye")!! == 333); + assert(x.get("bye")!! == 444); + assert(alloc.allocated() > 0); + x.free(); + y.free(); + assert(alloc.allocated() == 0); + }; +} + +/* + Some Keys (including Strings) are deep_copied into the hashmap on insertion. + When copying keys out, the keys themselves must also be deep-copied out. + Otherwise when the map is freed, the copied-in keys will also be freed, + resulting in use-after-free. +*/ +fn void copy_keys() @test +{ + String[] y; + @pool() + { + IntMap x; + x.set("hello", 0); // keys copied into temp hashmap + y = x.keys(mem); // keys copied out + // end of pool: hashmap and its copied-in keys dropped + }; + assert(y == {"hello"}); + foreach(key : y) free(key); + free(y); +} diff --git a/test/unit7/stdlib/collections/elastic_array.c3 b/test/unit7/stdlib/collections/elastic_array.c3 new file mode 100644 index 000000000..4fa4bdeab --- /dev/null +++ b/test/unit7/stdlib/collections/elastic_array.c3 @@ -0,0 +1,143 @@ +module elastic_array_test @test; +import std::collections::elastic_array; + +def IntList = ElasticArray{int, 10}; +def PtrList = ElasticArray{void*, 10}; + +fn void delete_contains_index() +{ + IntList test; + test.add_array({ 1, 2 }); + assert(test.contains(1)); + assert(test.contains(2)); + assert(!test.contains(0)); + assert(!test.contains(3)); + assert(test.array_view() == (int[]){ 1, 2 }); + test.push(3); + assert(test.array_view() == (int[]){ 1, 2, 3 }); + assert(test.contains(3)); + test[0] = 10; + assert(test.contains(10)); + test.remove_item(10); + assert(test.array_view() == (int[]){ 2, 3 }); + assert(!test.contains(1)); + assert(test.contains(2)); + assert(test.len() == 2); + test.push(0); + test.insert_at(0, 0); + assert(test.array_view() == (int[]){ 0, 2, 3, 0 }); + assert(test.index_of(0)!! == 0); + assert(test.rindex_of(0)!! == 3); + test.remove_item(0); + assert(test.len() == 2); + assert(test.array_view() == (int[]){ 2, 3 }); +} + +fn void compact() +{ + PtrList test; + test.add_array({ null, &test }); + assert(test.compact_count() == 1); + test.push(null); + assert(test.compact_count() == 1); + assert(test.len() == 3); + assert(test.compact() == 2); + assert(test.len() == 1); + assert(test.compact() == 0); +} + +fn void reverse() +{ + IntList test; + test.reverse(); + test.add_array({ 1, 2 }); + test.push(3); + assert(test.array_view() == (int[]) { 1, 2, 3}); + test.reverse(); + assert(test.array_view() == (int[]) { 3, 2, 1 }); + test.push(10); + assert(test.array_view() == (int[]) { 3, 2, 1, 10 }); + test.reverse(); + assert(test.array_view() == (int[]) { 10, 1, 2, 3 }); +} + +fn void remove_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&filter); + assert(removed == 3); + assert(test.array_view() == (int[]){1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&select); + assert(removed == 2); + assert(test.array_view() == (int[]){11, 10, 20}); +} + + +fn void remove_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == (int[]){1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == (int[]){11, 10, 20}); +} + +fn void retain_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&select); + assert(removed == 3); + assert(test.array_view() == (int[]){1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&filter); + assert(removed == 2); + assert(test.array_view() == (int[]){11, 10, 20}); +} + +fn void retain_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == (int[]){1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == (int[]){11, 10, 20}); +} + +module elastic_array_test; + +fn bool filter(int* i) +{ + return *i >= 10; +} + +fn bool select(int* i) +{ + return *i < 10; +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/enummap.c3 b/test/unit7/stdlib/collections/enummap.c3 new file mode 100644 index 000000000..ae45d161e --- /dev/null +++ b/test/unit7/stdlib/collections/enummap.c3 @@ -0,0 +1,29 @@ +module enummap_test @test; +import std::collections::enummap; + +enum FooEnum +{ + ONE, + TWO, + THREE, +} + +def FooEnumMap = EnumMap{FooEnum, uint}; + +fn void enums() +{ + FooEnumMap nm; + nm.set(ONE, 1); + nm.set(TWO, 2); + nm.set(THREE, 3); + + assert(nm[FooEnum.ONE] == 1); + assert(nm[FooEnum.TWO] == 2); + assert(nm[FooEnum.THREE] == 3); + + nm.init(0); + + assert(nm[FooEnum.ONE] == 0); + assert(nm[FooEnum.TWO] == 0); + assert(nm[FooEnum.THREE] == 0); +} diff --git a/test/unit7/stdlib/collections/generic_list.c3 b/test/unit7/stdlib/collections/generic_list.c3 new file mode 100644 index 000000000..3190cf1bd --- /dev/null +++ b/test/unit7/stdlib/collections/generic_list.c3 @@ -0,0 +1,15 @@ +module glist @test; +import std::collections; + +fn void simple_use() +{ + AnyList l; + l.push(1); + l.push("Hello"); + assert(l.get(1, String)!! == "Hello"); + assert(l.get(0, int)!! == 1); + assert(l.pop(String)!! == "Hello"); + assert(l.pop(int)!! == 1); + assert(l.len() == 0); + l.free(); +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/linkedlist.c3 b/test/unit7/stdlib/collections/linkedlist.c3 new file mode 100644 index 000000000..d1bb6ce6e --- /dev/null +++ b/test/unit7/stdlib/collections/linkedlist.c3 @@ -0,0 +1,237 @@ +module linkedlist_test @test; +import std::collections::linkedlist; + +def IntList = LinkedList{int}; + +fn void test_push_front() +{ + IntList list; + defer list.free(); + list.push_front(23); + assert(list.len() == 1); + assert(list.first()!! == 23); + assert(list.last()!! == 23); + list.push_front(55); + assert(list.len() == 2); + assert(list.last()!! == 23); + assert(list.first()!! == 55); + +} + +fn void test_push() +{ + IntList list; + defer list.free(); + list.push(23); + assert(list.len() == 1); + assert(list.first()!! == 23); + assert(list.last()!! == 23); + list.push(55); + assert(list.len() == 2); + assert(list.last()!! == 55); + assert(list.first()!! == 23); +} + +fn void test_get() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.get(2) == -3); + assert(list.get(1) == 55); + assert(list.get(0) == 23); +} + +fn void test_insert() +{ + IntList list; + defer list.free(); + list.push(-3); + list.push(55); + list.push(23); + list.insert_at(0, 1); + list.insert_at(2, 11); + list.insert_at(4, 111); + list.insert_at(6, 1111); + assert(list.get(0) == 1); + assert(list.get(1) == -3); + assert(list.get(2) == 11); + assert(list.get(3) == 55); + assert(list.get(4) == 111); + assert(list.get(5) == 23); + assert(list.get(6) == 1111); +} + +fn void test_set() +{ + IntList list; + defer list.free(); + list.push(-3); + list.push(55); + list.push(23); + for (int i = 0; i < 3; i++) list.set(i, list.get(i) + 1); + assert(list.get(0) == -2); + assert(list.get(1) == 56); + assert(list.get(2) == 24); +} + +fn void test_remove_at() +{ + IntList list; + defer list.free(); + for (int i = 0; i < 10; i++) list.push(i); + list.remove_at(0); + list.remove_at(1); + list.remove_at(7); + list.remove_at(5); + assert(list.get(0) == 1); + assert(list.get(1) == 3); + assert(list.get(5) == 8); + assert(list.get(4) == 6); +} + +fn void test_remove() +{ + IntList list; + defer list.free(); + list.push(2); + for (int i = 0; i < 10; i++) list.push(5); + list.push(2); + list.remove(5); + assert(list.len() == 2); +} + +fn void test_remove_first_match() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_first_match(23)); + assert(list.pop()!! == -3); + assert(list.pop()!! == 55); + assert(!list.len()); + + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_first_match(55)); + assert(list.pop()!! == -3); + assert(list.pop()!! == 23); + assert(!list.len()); + + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_first_match(-3)); + assert(list.pop()!! == 55); + assert(list.pop()!! == 23); + assert(!list.len()); +} + +fn void test_remove_last_match() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_last_match(23)); + assert(list.pop()!! == -3); + assert(list.pop()!! == 55); + assert(!list.len()); + + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_last_match(55)); + assert(list.pop()!! == -3); + assert(list.pop()!! == 23); + assert(!list.len()); + + list.push(23); + list.push(55); + list.push(-3); + assert(list.remove_last_match(-3)); + assert(list.pop()!! == 55); + assert(list.pop()!! == 23); + assert(!list.len()); +} + +fn void test_pop() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.len() == 3); + assert(list.first()!! == 23); + assert(list.last()!! == -3); + assert(list.pop()!! == -3); + assert(list.len() == 2); + assert(list.first()!! == 23); + assert(list.last()!! == 55); + assert(list.pop()!! == 55); + assert(list.first()!! == 23); + assert(list.last()!! == 23); + assert(list.pop()!! == 23); + assert(list.len() == 0); + assert(@catch(list.pop())); + assert(list.len() == 0); + list.push(55); + assert(list.len() == 1); +} + +fn void test_remove_last() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.len() == 3); + assert(list.first()!! == 23); + assert(list.last()!! == -3); + assert(@ok(list.remove_last())); + assert(list.len() == 2); + assert(list.first()!! == 23); + assert(list.last()!! == 55); + assert(@ok(list.remove_last())); + assert(list.first()!! == 23); + assert(list.last()!! == 23); + assert(@ok(list.remove_last())); + assert(list.len() == 0); + assert(@catch(list.pop())); + assert(list.len() == 0); + list.push(55); + assert(list.len() == 1); +} + +fn void test_remove_first() +{ + IntList list; + defer list.free(); + list.push(23); + list.push(55); + list.push(-3); + assert(list.len() == 3); + assert(list.first()!! == 23); + assert(list.last()!! == -3); + assert(@ok(list.remove_first())); + assert(list.len() == 2); + assert(list.last()!! == -3); + assert(list.first()!! == 55); + assert(@ok(list.remove_first())); + assert(list.last()!! == -3); + assert(list.first()!! == -3); + assert(@ok(list.remove_first())); + assert(list.len() == 0); + assert(@catch(list.remove_first())); + assert(list.len() == 0); + list.push(55); + assert(list.len() == 1); +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/list.c3 b/test/unit7/stdlib/collections/list.c3 new file mode 100644 index 000000000..2d9aea1c4 --- /dev/null +++ b/test/unit7/stdlib/collections/list.c3 @@ -0,0 +1,178 @@ +module list_test @test; +import std::collections::list; + +def IntList = List{int}; +def PtrList = List{void*}; + +struct Overalign +{ + float[<4>] x @align(128); +} + +def OveralignList = List{Overalign}; +fn void overaligned_type() +{ + OveralignList l; + defer l.free(); + + Overalign y; + for (int i = 0; i < 1000; i++) l.push(y); + assert((usz)l.get_ref(2) - (usz)l.get_ref(1) == Overalign.sizeof); +} + + +fn void delete_contains_index() +{ + IntList test; + + test.add_array({ 1, 2 }); + assert(test.contains(1)); + assert(test.contains(2)); + assert(!test.contains(0)); + assert(!test.contains(3)); + assert(test.array_view() == { 1, 2 }); + test.push(3); + assert(test.array_view() == { 1, 2, 3 }); + assert(test.contains(3)); + test[0] = 10; + assert(test.contains(10)); + test.remove_item(10); + assert(test.array_view() == { 2, 3 }); + assert(!test.contains(1)); + assert(test.contains(2)); + assert(test.len() == 2); + test.push(0); + test.insert_at(0, 0); + assert(test.array_view() == { 0, 2, 3, 0 }); + assert(test.index_of(0)!! == 0); + assert(test.rindex_of(0)!! == 3); + test.remove_item(0); + assert(test.len() == 2); + assert(test.array_view() == { 2, 3 }); +} + +fn void compact() +{ + PtrList test; + test.add_array({ null, &test }); + assert(test.compact_count() == 1); + test.push(null); + assert(test.compact_count() == 1); + assert(test.len() == 3); + assert(test.compact() == 2); + assert(test.len() == 1); + assert(test.compact() == 0); +} + +fn void reverse() +{ + IntList test; + + test.reverse(); + test.add_array({ 1, 2 }); + test.push(3); + assert(test.array_view() == { 1, 2, 3}); + test.reverse(); + assert(test.array_view() == { 3, 2, 1 }); + test.push(10); + assert(test.array_view() == { 3, 2, 1, 10 }); + test.reverse(); + assert(test.array_view() == { 10, 1, 2, 3 }); +} + +fn void remove_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&filter); + assert(removed == 3); + assert(test.array_view() == {1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_if(&select); + assert(removed == 2); + assert(test.array_view() == {11, 10, 20}); +} + +fn void init_with_array() +{ + IntList foo; + foo.init_with_array(mem, { 1, 2, 3}); + defer foo.free(); + assert(foo.len() == 3); + assert(foo[2] == 3); +} + +fn void init_with_temp_array() +{ + IntList foo; + foo.tinit_with_array({ 1, 2, 3}); + assert(foo.len() == 3); + assert(foo[2] == 3); +} + +fn void remove_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == {1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == {11, 10, 20}); +} + +fn void retain_if() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&select); + assert(removed == 3); + assert(test.array_view() == {1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.retain_if(&filter); + assert(removed == 2); + assert(test.array_view() == {11, 10, 20}); +} + +fn void retain_using_test() +{ + IntList test; + usz removed; + + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10); + assert(removed == 3); + assert(test.array_view() == {1, 2}); + + test.clear(); + test.add_array({ 1, 11, 2, 10, 20 }); + removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10); + assert(removed == 2); + assert(test.array_view() == {11, 10, 20}); +} + +module list_test; + +fn bool filter(int* i) +{ + return *i >= 10; +} + +fn bool select(int* i) +{ + return *i < 10; +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/map.c3 b/test/unit7/stdlib/collections/map.c3 new file mode 100644 index 000000000..54f9f1f3e --- /dev/null +++ b/test/unit7/stdlib/collections/map.c3 @@ -0,0 +1,105 @@ +module map_test @test; +import std::collections::list; +import std::collections::map; +import std::sort; +import std::io; + +def TestHashMap = HashMap{String, usz}; + +struct MapTest +{ + String key; + usz value; +} +def List = List{MapTest}; + +fn void map() +{ + TestHashMap m; + assert(!m.is_initialized()); + m.tinit(); + assert(m.is_initialized()); + assert(m.is_empty()); + assert(m.len() == 0); + + m.set("a", 1); + assert(!m.is_empty()); + assert(m.len() == 1); + m.remove("a"); + assert(m.is_empty()); + + MapTest[] tcases = { {"key1", 0}, {"key2", 1}, {"key3", 2} }; + foreach (tc : tcases) + { + m.set(tc.key, tc.value); + } + assert(m.len() == tcases.len); + foreach (tc : tcases) + { + usz v = m.get(tc.key)!!; + assert(tc.value == v); + } + + List list; + list.tinit(); + m.@each(;String key, usz value) + { + list.push({key, value}); + }; + assert(list.len() == tcases.len); + quicksort(list, fn int (MapTest a, MapTest b) => (int)(a.value - b.value)); + foreach (i, tc : tcases) + { + assert(tc.key == list[i].key); + assert(tc.value == list[i].value); + } +} + +def FooMap = HashMap{char, Foobar}; +enum Foobar : inline char +{ + FOO, + BAR, + BAZ +} + +enum Foobar2 : int (inline char y) +{ + ABC = 3, + DEF = 5, +} + +fn void map_inline_enum() +{ + FooMap x; + x[Foobar.BAZ] = FOO; + x[Foobar2.ABC] = BAR; + test::eq(string::tformat("%s", x), "{ 2: FOO, 3: BAR }"); + x.free(); +} + +fn void map_remove() +{ + TestHashMap m; + assert(!@ok(m.remove("A"))); + m.tinit(); + assert(!@ok(m.remove("A"))); + m.set("A", 0); + assert(@ok(m.remove("A"))); +} + +fn void map_copy() +{ + TestHashMap hash_map; + hash_map.tinit(); + + hash_map.set("aa", 1); + hash_map.set("b", 2); + hash_map.set("bb", 1); + + TestHashMap hash_map_copy; + hash_map_copy.tinit_from_map(&hash_map); + + assert(hash_map_copy.len() == hash_map.len()); + +} diff --git a/test/unit7/stdlib/collections/object.c3 b/test/unit7/stdlib/collections/object.c3 new file mode 100644 index 000000000..280d452b4 --- /dev/null +++ b/test/unit7/stdlib/collections/object.c3 @@ -0,0 +1,43 @@ +module object_test @test; +import std::collections::object; + +fn void test_general() +{ + Object* root = object::new_obj(allocator::heap()); + defer root.free(); + + root.set("foo", 1); + root.set("bar", "baz"); + assert(root.get_int("foo")!! == 1); + assert(root.get_string("bar")!! == "baz"); + Object* goo = root.set("goo", object::new_obj(allocator::heap())); + goo.push("hello"); + goo.push(132); + assert(root.get("goo").get_int_at(1)!! == 132); + assert(root.get("goo").get_string_at(0)!! == "hello"); + Object* abc = root.get_or_create_obj("abc80"); + abc.set("cool", 1.3); + assert(root.get("abc80").get_int("cool")!! == 1); + assert(root.get("abc80").get_float("cool")!! == 1.3); + assert((root.get_int("yyy") ?? -1) == -1); + root.set("yyy", true); + assert(root.get_bool("yyy") ?? false); +} + +fn void test_to_format_int() +{ + { + Object* int_object = object::new_int(16, allocator::heap()); + defer int_object.free(); + String s = string::format(mem, "%s", int_object); + defer free(s); + assert(s == "16"); + } + { + Object* int_object = object::new_int(-16, allocator::heap()); + defer int_object.free(); + String s = string::format(mem, "%s", int_object); + defer free(s); + assert(s == "-16"); + } +} diff --git a/test/unit7/stdlib/collections/priorityqueue.c3 b/test/unit7/stdlib/collections/priorityqueue.c3 new file mode 100644 index 000000000..671c93a2d --- /dev/null +++ b/test/unit7/stdlib/collections/priorityqueue.c3 @@ -0,0 +1,62 @@ +module priorityqueue_test @test; +import std::collections; +import std::collections::priorityqueue; + +def Queue = PriorityQueue{int}; + +fn void priorityqueue() +{ + Queue q; + defer q.free(); + + assert(q.is_empty()); + + q.push(1); + q.push(2); + assert(q.len() == 2); + + int x; + x = q.pop()!!; + assert(x == 1, "got %d; want %d", x, 1); + x = q.pop()!!; + assert(x == 2, "got %d; want %d", x, 2); + + q.push(3); + q.push(2); + q.push(1); + x = q.pop()!!; + assert(x == 1, "got %d; want %d", x, 1); + x = q.pop()!!; + assert(x == 2, "got %d; want %d", x, 2); + x = q.pop()!!; + assert(x == 3, "got %d; want %d", x, 3); +} + +def QueueMax = PriorityQueueMax{int}; + +fn void priorityqueue_max() +{ + QueueMax q; + defer q.free(); + assert(q.is_empty()); + + q.push(1); + q.push(2); + assert(q.len() == 2); + + int x; + x = q.pop()!!; + assert(x == 2, "got %d; want %d", x, 2); + x = q.pop()!!; + assert(x == 1, "got %d; want %d", x, 1); + + q.push(3); + q.push(2); + q.push(1); + x = q.pop()!!; + assert(x == 3, "got %d; want %d", x, 3); + x = q.pop()!!; + assert(x == 2, "got %d; want %d", x, 2); + x = q.pop()!!; + assert(x == 1, "got %d; want %d", x, 1); +} \ No newline at end of file diff --git a/test/unit7/stdlib/collections/range.c3 b/test/unit7/stdlib/collections/range.c3 new file mode 100644 index 000000000..2b791889e --- /dev/null +++ b/test/unit7/stdlib/collections/range.c3 @@ -0,0 +1,36 @@ +module range_test @test; +import std::collections::range; + +def IntRange = Range{int}; +def IntExRange = ExclusiveRange{int}; + +fn void test_range() +{ + IntRange range = { -4, 2 }; + int sum = 0; + foreach (int z : range) + { + assert(z >= -4 && z < 3); + sum += z * z; + } + assert(sum == 35); + assert(range.contains(-4)); + assert(range.contains(2)); + assert(!range.contains(3)); +} + +fn void test_exrange() +{ + IntExRange range = { -4, 2 }; + int sum = 0; + foreach (int z : range) + { + assert(z >= -4 && z < 2); + sum += z * z; + } + assert(sum == 31); + assert(range.contains(-4)); + assert(range.contains(1)); + assert(!range.contains(2)); +} + diff --git a/test/unit7/stdlib/collections/ringbuffer.c3 b/test/unit7/stdlib/collections/ringbuffer.c3 new file mode 100644 index 000000000..dc5a8ae9c --- /dev/null +++ b/test/unit7/stdlib/collections/ringbuffer.c3 @@ -0,0 +1,29 @@ +module ringbuffer_test @test; +import std::collections::ringbuffer; +import std::io; + +def Buffer = RingBuffer{char[4]}; + +fn void push_get() +{ + Buffer rb; + rb.init(); + rb.push(1); + rb.push(2); + rb.push(3); + rb.push(4); + + assert(rb.get(0) == 1); + assert(rb.get(1) == 2); + assert(rb.get(2) == 3); + assert(rb.get(3) == 4); + + rb.push(5); + assert(rb.get(0) == 2); + assert(rb.get(1) == 3); + assert(rb.get(2) == 4); + assert(rb.get(3) == 5); + + char c = rb.pop()!!; + assert(c == 5); +} \ No newline at end of file diff --git a/test/unit7/stdlib/compression/qoi.c3 b/test/unit7/stdlib/compression/qoi.c3 new file mode 100644 index 000000000..a3f8a8a9a --- /dev/null +++ b/test/unit7/stdlib/compression/qoi.c3 @@ -0,0 +1,38 @@ +module qoi_test @test; + +import std::io::file; +import std::compression::qoi; + + +const char[?] TEST_QOI_DATA = b64"cW9pZgAAAVQAAACpBABV/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/cl/Jv39/f392jXBJv39/f392TXBJv39/f392TXBJv39/f392TXBJv39/f392TXBJv39/f392TXBJv39/f392DXBJv39/f392TXBJv39/f392TXBJv39/f392TXBJv39/f392TXBJu81Jv39zzXAJv390jXBJu01wyb9/cg1xib9/dA1wibsNcQm/f3GNcgm0zXAJv33NcEm6zXHJv39wzXIJtQ1xCb99DXBJus1wibANcEm/f3BNcgm1TXHJv3yNcEm6jXCJsE1wSb9/cA1xibZNSbANcQm/fE1wSbpNcImwjXBJv39NcUm4DXCJv3xNcEm6DXCJsM1wSb9/TXEJuI1wib98DXBJuc1wibENcEm/fw1wyblNcIm/e41wibmNcImxTXBJv37NcMm5zXBJv3uNcIm5jXCJsU1wSb9+jXEJuc1wib97TXCJuU1wibGNcEm/fk1xCboNcIm/e01wSbmNcImxjXBJv35NcMm6DXCJv3uNcEm5jXBJsc1wSb9+DXDJuk1wib97jXBJuU1wibGNcIm/fg1wyboNcIm/e81wSblNcEmxzXBJv34NcMm6TXCJv3vNcEm5TXBJsc1wSb9+DXCJuk1wib98DXBJuQ1wibHNcEm/fc1wyboNcMm/fA1wSbkNcEmxzXCJv33NcMm5jXEJv3xNcEm5DXBJsc1wSb9+DXCJuY1xSb98TXBJuM1wibGNcIm/fc1wybmNcQm/fI1wSbjNcEmxjXCJv34NcIm5zXGJv3wNcEm4zXBJsU1wyb9+DXCJug1xib97jXCJuI1wibENcMm/fk1wibrNcQm/e01wibiNcImwjXDJv37NcIm7DXEJv3sNcEm4zXBJsE1xCb9wDUm+TXCJu41wyb96zXBJuM1wSY1xSbKNcUm6jXDJvc1wibvNcMm/eo1wSbjNccmyzXIJuY1xSb3NcIm7zXEJv3pNcEm4jXGJsw1yibLNcUm0DXGJvc1wibwNcMm/ek1wSbiNcQmzTXLJsk1xybDNcImxjXHJvc1wibxNcIm/ek1wSbhNcMmzzXDJsA1xSbGNcomwjXDJsQ1ySb2NcIm8TXCJv3pNcEm4TXCJs81wibCNcUmxDXLJsI1xSbCNcQmNcIm9zXDJvA1wib96DXCJuI1wSbONcMmwzXEJsM1xibANcImwjXGJsA1xCY1wyb4NcIm7zXDJv3oNcIm4jXBJs41wibENcQmwTXGJsE1wybCNccmNcMmNcMm+TXCJtA1wibZNcIm/ek1wSbjNcEmzjXBJsU1wybANcYmwzXCJsI11Cb5NcMmzTXDJtk1wyb96TXBJuM1wSbNNcImxTXMJsM1wybCNcImwDXNJvs1wibMNcMm2TXDJv3qNcEm4zXBJs01wibFNcsmxDXDJsE1wybBNcsm/DXDJsk1xCbZNcQm/eo1wSbjNcEmzDXCJsc1yCbGNcImwjXCJsM1yCb9wDXEJsY1xCbaNcQm/es1wSbjNcEmyzXDJsY1xibJNcImwTXDJsQ1wib9xjXQJto1xCb97DXBJuM1wSbKNcQmxjXCJs01wibANcMmxTXCJv3HNc4m2jXEJv3tNcEm4zXBJsk1xibENcImzjXCJsA1wybENcMm/cg1yybbNcQm/e41wSbjNcImxzXCJjXCJsQ1wibNNcMmNcMmxTXDJv3JNcgm3DXEJv3vNcEm5DXBJsQ1xCbANcwmzTXIJsY1wyb98DXEJtg1wCb91DXBJuQ1wyY1xibCNcomzjXIJsc1wib96DXBJsE1xSbWNcAm/dc1wSblNckmxTXIJs81xybINcImzTXBJv3VNcMmNcUm1TXAJv3ZNcEm5jXHJsg1wibTNcYmyTXDJss1wSb91TXLJtQ1wCb92zXBJug1wSblNcYmyTXDJsk1wyb91TXJJtM1wCb93jXBJv3UNcQmzDXEJsU1xCb91TXJJtI1wCb94DXBJv3UNcMmzjXGJjXGJv3WNccm0jXAJv3iNcEm/dU1wSbPNc4m/dc1xibQNcAm/eU1wSb96zXLJv3YNcQm0DXAJv3nNcIm/ew1xyb92zXBJtA1wCb96jXBJv3uNcIm/fE1wCb97DXBJv39/eU1wCb9/f39/dc1wCb9/f39/dg1wCb9/f39/dg1wCbxNcUm/f39/dw1wCbxNcgm/f39/dk1wCbxNcom/f39/dc1wCbyNcQmwjXBJv39/f3VNcAm9DXCJsQ1wSb9/f390jXBJvY1wSbENcIm/f39/dA1wSb4NcAmxTXCJv39/f3ONcEm+zUmxTXCJs01wCb9/f35NcEm/cc1wibJNcUm1QDeJv39/DXCJv3JNcImyDXGJtUA3ib9/fo1wSb9zDXCJsU1yCbWAN4m/f33NcIm/c41wibDNckm1wDeJsdv4Cb9/co1wib9zzXCJsE1ySbaAN4mxzLgJv39yDXCJv3RNc4m3ADeJscy4Cb9/cY1wib9xTXBJsk1zCbeAN4mxzLgJv39xDXCJv3ENcYmxjXKJuEAyP8AAAC7/wAAAOr/AAAA9g4JAAn/AAAA+sAJAMomxzLJ/icAAMIy0Sb9/cI1wib9xTXIJsU1xybkAMc+yP8AAAD5AMomxzLI/o0AACrDMtAm/f3BNcEm/cY1ySbFNcUm5gDGPsoAyibHMsj+UQAAKsgyyyb9/TXBJv3INcImwDXDJsU1wyboAMY+whI+xQDKJscyyCrJMssm/fs1wSb9yjXBJsE1xCbENcEm6gDGPsIADj7DDgDKJscyyCrJMssm/fg1wib9zDXBJsE1xCbzAMUJPsEJPsUAyybHMsgqwSgqwP41AADAKBwyzCb99jXCJv3ONcImwDXEJsM1Ju0AxQ4+yQDLJscyyCrBMtMm/fQ1wib90DXCJsA1xSY1wibtAMUOPsgzCQDKJscyyCrBMtMm/fI1wib9yDXEJsI1wiY1yibuAMUJPsQJEj7DAMkmxzLIKsEy0yb98DXCJuY1wibcNcUmwTXOJu8AzQ4+wwDJJscyyCrBMtMm/e41wibpNcEm2zXHJsE1xSY1xSbvAM0JPsMAySbHMsgqwTLTJv3sNcIm6zXCJto1wSbANcMmwTXEJjXEJvAA3ibHMsgqwTLTJv3qNcIm7jXCJtA1JsE1JsA1wibCNcImwjXBJsI1wibxAN4mxzLIKsEy0yb96DXCJvA1wybONcomwzXCJv3BAN4mxzLIHMEy0yb95jXCJvM1wibPNckmwzXCJv3BAN4mxzLgJvs1wSbiNcIm9jXCJs81yCbDNcIm/cEA3ibHMuAm+zXCJt81wibxNSbENcImxTXDJsI1xCbANcImwjXCJv3qMuAm/DXCJtw1wibxNcEmxTXBJsQ1xSbBNcMmwjXBJsM1wSb96jLgJvw1wibcNcIm8DXCJsU1wibCNccmwTXCJsI1wSbENSb9/f3RNcEm3DXDJu81wSbFNcImwjXCJsA1wSbCNcEmwjXCJv39/dc1wibfNcAm7jXBJsY1wSbCNcEmwTXCJsE1wSbBNcMm/f392DXBJv3TNcImxTXBJsI1wSbCNcEmwDXCJsI1wib9/f3YNcEm/dM1wibFNcImwTXBJsE1wSY1xCbCNcIm/f392DXCJv3TNcEmxTXCJsE1wSY1wyY1xCbDNcAm/f39wjUm1TXBJv01wCbQNcEmxDXDJsE1xibANcEmNcAm/f39yDXDJtI1wSb4NckmzDXCJsE1xSbBNcUmwDXBJv39/cw1xCbRNcIm9jXPJsg1wSbANccmwDXDJsE1wib9/f3lNcIm9TXTJsU1xyY1wibANcEmwzXBJv39/eY1wib1NcUmNcsmxTXGJsA1wibANcImwTXCJv3Zftwm/es1wib1NcImwTXNJsM1xibCNcEmwDXCJsA1wib92i7cJsx73Sb9NcEm9TXCJsA1xSbDNcImwzXFJsM1wSbANcgm/dou3CbMMN0m/TXBJvQ1wyY1xibDNcMmwzXCJsU1wSbBNcYm/dsu3CbMMN0m/DXCJvQ1wibANcEmNcImxDXCJsQ1wSbFNcEmwjXEJv3cLsr+AADQ/gAAjcAlLswmzDDI/gCNAMD+ACcAwjDNJvw1wib0NcImwDXBJjXCJsM1wibFNcImxDXBJsM1wSb93i7K/gAAJ8EQLswmzDDHOMUwzSb8NcIm9DXCJsA1wSbANcImwTXDJsY1wSbENcEm/eYuygbBEC7MJswwxjjIMMsm/DXCJvQ1wibANcEmwDXCJjXEJsg1wCbENcEm/eYuygbBJS7MJswwxTjENsA4wTDLJvw1wib0NcImwDXBJsA1yCbINcEmxTXAJv3mLskQBsEuzSbMMMU4wzDA/gBRADjBMMsm/DXCJvQ1wibANcImNccmyzXAJv3uLsn+AAAQ/gAAAcEGwBAuyibMMMU4wTYwwTjCMMsm/DXCJvQ1wibBNcomzDXAJv3uLsklPMCgjAbCLskmzDDFOMSkRDjCMMsm+zXCJvU1wibCNccmzzUm/e4uySU8wCUGxC7HJswwxTjJMMsm6TXAJs41wib1NcImwzXFJv39wy7IBsElBsUuxybMMMU4yTDLJug1wCbPNcIm9TXDJv39zi7IPMEYLsEGwi7HJswwyDjB/gA1ADjCMMsm5jXBJs81wib2NcMm/f3OLsg8wSUGEP4AAFEGwi7HJswwzDjBCjDLJuU1wybONcIm9zXDJss1JjXAJv36Lsg8wQbGLscmzDDGNjjANjA2OME2MMsm5TXCJs41wyb3NcQmyTXCJv37LsgGJTwGxi7HJswwxgo4wAo2OMIwzCb5NcIm+TXFJsQ1xCb9/C7KEAbEEC7IJswwxj44wG44wzDMJvg1wib7Nc8m/f0u3CbMMMYKOMU2MMwm+DXCJvw1zSb9/cAu3CbMMMY2OMQ2MM0m9zXCJv3ANcsm/f3tMMc4wzYwzib3NcIm/cM1xib9/e8w3Sb2NcIm/f39/f3XNcMm/f397f6NjY3BJv3iNcMm/f397Tj+JycnwSb93zXFJv39/dU4/lFRUT7DJtA+wib93zXEJv39/dQ4PsYmzzg+wTQm/d81wyb9/f3UPsgmzz7COCb94DXAJv39/dU+xjgmyDg0wDgmwjg+wibCOD7GJv39/f3tPsQmyD7FJsI+wjgmwT7IJs84ND7CJsU4NMA4Jv39/f3LPsImyT7GJsE0PsImwj7IJsE4PsI4JsY+xCbEPsMmxTg0hIg0OCb9/f39PsEmyj7GJsE+wjgmwj7COMA0PsEmwTQ+yCbBPsQmxD7DJsQ+xSb9/f38PsI4JsI4wSbBPsEQOD7BJsE+wTQmwz7BOD7EJsEQPsgmwT7DJsU+wTQ4JsM+xib9/f38PsomwRA+xSbAOD7BOCbDPsgmwRA+yCbBPsUmwz7CJsQ+whA+wSb9/f38PsomwTg+xSbAND7CNMA4JsA+xzgmwRA+wBAmyT7FJsI+wibEPsI4Jv39/f3BOD7IOCbCPsUmwDQ+xSbAPsUmxBA+wBAmyz7DJsI+wzgmwj7DOCb9/f390j7COCbAOD7FJsE4NMA4JsY0PsA0Jsk+xSbCOD7DJsI4PsM4Jv39/f3bPsE0OCbNOD7AOCbJPsQ4JsE+xibBPsQ4Jv39/f39wD7EJsI+xibBPsQ4Jv39/f39yj7GJsE+wzgm/f39/f3LODT+KysrPsA4JsM+wSb9/f39/f39/f3aAAAAAAAAAAE="; + + +fn void test_qoi_all() +{ + @pool() + { + // create a test descriptor + QOIDesc test_desc; + + // decode the test data + char[] decoded = qoi::new_decode(TEST_QOI_DATA[..], &test_desc)!!; + defer free(decoded); + + assert(test_desc.width == 340 && test_desc.height == 169, "Expected resolution of 340x169"); + + // encode the decoded data + char[] encoded = qoi::new_encode(decoded, &test_desc)!!; + assert(encoded == TEST_QOI_DATA[..], "Encoder output should match the test data"); + defer free(encoded); + // encode and write the decoded data to a file + usz written = qoi::write("unittest.qoi", decoded, &test_desc)!!; + + // read and decode the written data + char[] read = qoi::new_read("unittest.qoi", &test_desc)!!; + assert(read == decoded, "Read data should match the decoded data"); + + // cleanup + file::delete("unittest.qoi")!!; + free(read); + }; +} diff --git a/test/unit7/stdlib/conv_tests.c3 b/test/unit7/stdlib/conv_tests.c3 new file mode 100644 index 000000000..e422c4f6d --- /dev/null +++ b/test/unit7/stdlib/conv_tests.c3 @@ -0,0 +1,79 @@ +module conv_tests; +import std::io; + +fn void! comparison_helper_32_to_8(Char32 c32, String expected_output) +{ + char[8] out; + usz len = conv::char32_to_utf8(c32, &out)!; + assert(len == expected_output.len, "Len should be 1"); + foreach (i, c : expected_output) + { + assert(out[i] == c, "Expected other value"); + } +} + +fn void! comparison_helper_8_to_32(String in, Char32 c32) +{ + usz len = in.len; + Char32 res = conv::utf8_to_char32(in.ptr, &len)!; + assert(len == in.len, "All len should be used."); + assert(res == c32, "Expected character match."); +} + +fn void assert_utf8_is_error(String in) +{ + usz len = in.len; + assert(@catch(conv::utf8_to_char32(in.ptr, &len)), "Expected error"); +} + +fn void test_char32_ut8_boundary() @test +{ + // First sequence per len + comparison_helper_32_to_8(0x00000000, { 0 })!!; + comparison_helper_32_to_8(0x00000080, { 0xc2, 0x80 })!!; + comparison_helper_32_to_8(0x00000800, { 0xe0, 0xa0, 0x80 })!!; + comparison_helper_32_to_8(0x00010000, { 0xf0, 0x90, 0x80, 0x80 })!!; + assert(@catch(comparison_helper_32_to_8(0x10ffff + 1, { 0 })), "Expected error"); + // Last seq per len + comparison_helper_32_to_8(0x0000007f, { 0x7f })!!; + comparison_helper_32_to_8(0x000007ff, { 0xdf, 0xbf })!!; + comparison_helper_32_to_8(0x0000ffff, { 0xef, 0xbf, 0xbf })!!; + comparison_helper_32_to_8(0x0010ffff, { 0xf4, 0x8f, 0xbf, 0xbf })!!; + // Other boundaries + comparison_helper_32_to_8(0x0000d7ff, { 0xed, 0x9f, 0xbf})!!; + comparison_helper_32_to_8(0x0000e000, { 0xee, 0x80, 0x80 })!!; + comparison_helper_32_to_8(0x0000fffd, { 0xef, 0xbf, 0xbd })!!; + +} + +fn void test_utf8_to_char32_boundary() @test +{ + // First sequence per len + comparison_helper_8_to_32("\0", 0x0 )!!; + comparison_helper_8_to_32({ 0xc2, 0x80 }, 0x80)!!; + comparison_helper_8_to_32({ 0xe0, 0xa0, 0x80 }, 0x800 )!!; + comparison_helper_8_to_32({ 0xf0, 0x90, 0x80, 0x80 }, 0x10000)!!; + // Last seq per len + comparison_helper_8_to_32({ 0x7f }, 0x7f)!!; + comparison_helper_8_to_32({ 0xdf, 0xbf }, 0x7ff )!!; + comparison_helper_8_to_32({ 0xef, 0xbf, 0xbf }, 0xffff)!!; + comparison_helper_8_to_32({ 0xf4, 0x8f, 0xbf, 0xbf }, 0x10ffff)!!; + // Other boundaries + comparison_helper_8_to_32({ 0xed, 0x9f, 0xbf }, 0xd7ff)!!; + comparison_helper_8_to_32({ 0xee, 0x80, 0x80 }, 0xe000)!!; + comparison_helper_8_to_32({ 0xef, 0xbf, 0xbd }, 0xfffd)!!; + + assert_utf8_is_error({ 0x80 }); + assert_utf8_is_error({ 0xbf }); + assert_utf8_is_error({ 0xfe }); + assert_utf8_is_error({ 0xff }); + assert_utf8_is_error({ 0xfe, 0xfe, 0xff, 0xff }); + + // Overlong + assert_utf8_is_error({ 0xc0, 0xaf }); + assert_utf8_is_error({ 0xe0, 0x80, 0xaf }); + assert_utf8_is_error({ 0xf0, 0x80, 0x80, 0xaf }); + assert_utf8_is_error({ 0xf8, 0x80, 0x80, 0xaf }); + assert_utf8_is_error({ 0xfc, 0x80, 0x80, 0x80, 0xaf }); +} + diff --git a/test/unit7/stdlib/core/array.c3 b/test/unit7/stdlib/core/array.c3 new file mode 100644 index 000000000..b5c3502cc --- /dev/null +++ b/test/unit7/stdlib/core/array.c3 @@ -0,0 +1,32 @@ +module arraytests @test; + +fn void find() +{ + int[3] a = { 1, 2, 3 }; + assert(array::index_of(a, 2)!! == 1); + assert(array::index_of(a, 1)!! == 0); + assert(array::index_of(a, 3)!! == 2); + assert(@catch(array::index_of(a, 4)) == SearchResult.MISSING); +} + +fn void find_subarray() +{ + int[] a = { 1, 2, 3 }; + assert(array::index_of(a, 2)!! == 1); + assert(array::index_of(a, 1)!! == 0); + assert(array::index_of(a, 3)!! == 2); + assert(@catch(array::index_of(a, 4)) == SearchResult.MISSING); +} + +fn void concat() +{ + int[3] a = { 1, 2, 3 }; + free(array::concat_new(a, a)); + free(array::concat_new(a[..], a[..])); + free(array::concat_new(a[:0], a[:0])); + free(array::concat_new((int[2]) { 1, 2 }, a[:0])); + free(array::concat_new(a[:0], (int[2]) { 1, 2 })); + int[] c = array::concat_new(a[1..2], a); + defer free(c); + assert (c == (int[]){ 2, 3, 1, 2, 3 }); +} diff --git a/test/unit7/stdlib/core/bitorder.c3 b/test/unit7/stdlib/core/bitorder.c3 new file mode 100644 index 000000000..85fb9228b --- /dev/null +++ b/test/unit7/stdlib/core/bitorder.c3 @@ -0,0 +1,45 @@ +module std::core::bitorder @test; + +fn void test_read() +{ + char[?] bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + assert(bitorder::read(bytes, UShortBE) == 0x0102); + assert(bitorder::read(bytes, UShortLE) == 0x0201); + assert(bitorder::read(bytes, UIntBE) == 0x01020304); + assert(bitorder::read(bytes, UIntLE) == 0x04030201); + assert(bitorder::read(bytes, ULongBE) == 0x0102030405060708); + assert(bitorder::read(bytes, ULongLE) == 0x0807060504030201); + + assert(bitorder::read(&bytes, UShortBE) == 0x0102); + assert(bitorder::read(&bytes, UShortLE) == 0x0201); + assert(bitorder::read(&bytes, UIntBE) == 0x01020304); + assert(bitorder::read(&bytes, UIntLE) == 0x04030201); + assert(bitorder::read(&bytes, ULongBE) == 0x0102030405060708); + assert(bitorder::read(&bytes, ULongLE) == 0x0807060504030201); + + assert(bitorder::read(bytes[..], UShortBE) == 0x0102); + assert(bitorder::read(bytes[..], UShortLE) == 0x0201); + assert(bitorder::read(bytes[..], UIntBE) == 0x01020304); + assert(bitorder::read(bytes[..], UIntLE) == 0x04030201); + assert(bitorder::read(bytes[..], ULongBE) == 0x0102030405060708); + assert(bitorder::read(bytes[..], ULongLE) == 0x0807060504030201); +} + +fn void test_write() +{ + char[?] bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + char[8] buf; + + ushort x1 = bitorder::read(bytes, UShortBE); + bitorder::write(x1, &buf, UShortBE); + assert(bitorder::read(buf, UShortBE) == x1); + + uint x2 = bitorder::read(bytes, UIntBE); + bitorder::write(x2, buf[..], UIntBE); + assert(bitorder::read(buf, UIntBE) == x2); + + ulong x3 = bitorder::read(bytes, ULongBE); + bitorder::write(x3, buf[..], ULongBE); + assert(bitorder::read(buf, ULongBE) == x3); +} \ No newline at end of file diff --git a/test/unit7/stdlib/core/builtintests.c3 b/test/unit7/stdlib/core/builtintests.c3 new file mode 100644 index 000000000..17f1847f2 --- /dev/null +++ b/test/unit7/stdlib/core/builtintests.c3 @@ -0,0 +1,79 @@ +module std::core::builtins @test; + +fn void test_anycast() +{ + int a; + any b = &a; + assert(anycast(b, int)!! == &a); + assert(@catch(anycast(b, double)) == CastResult.TYPE_MISMATCH); +} + +fn void test_bitcast() +{ + int a = 123; + float z = bitcast(a, float); + assert(bitcast(z, int) == a); +} + +enum Tester +{ + ABC, + DEF, +} + +fn void test_enum_by_name() +{ + assert(enum_by_name(Tester, "ABC")!! == Tester.ABC); + assert(enum_by_name(Tester, "DEF")!! == Tester.DEF); + assert(@catch(enum_by_name(Tester, "GHI")) == SearchResult.MISSING); +} + +fn void test_likely() +{ + int a = 2; + int b = 43; + if (@likely(a > b, 0.5)) a++; + if (@likely(a < b)) b++; +} + +fn void test_unlikely() +{ + int a = 2; + int b = 43; + if (@unlikely(a > b, 0.5)) a++; + if (@unlikely(a < b)) b++; +} + +fn void test_expect() +{ + int a = 2; + int b = 43; + int c = @expect(a, 2, 0.5); + int d = @expect(b, 2u8); +} + + +int abc; + +fn void test_prefetch() +{ + @prefetch(&abc); +} + +fn void test_hash() +{ + (char){}.hash(); + (ichar){}.hash(); + (short){}.hash(); + (ushort){}.hash(); + (int){}.hash(); + (uint){}.hash(); + (long){}.hash(); + (ulong){}.hash(); + (int128){}.hash(); + (uint128){}.hash(); + String x = "abc"; + char[] y = "abc"; + assert(x.hash() == y.hash()); + assert(int.typeid.hash()); +} \ No newline at end of file diff --git a/test/unit7/stdlib/core/comparison.c3 b/test/unit7/stdlib/core/comparison.c3 new file mode 100644 index 000000000..af0fdf9b8 --- /dev/null +++ b/test/unit7/stdlib/core/comparison.c3 @@ -0,0 +1,22 @@ +module comparison @test; + +fn void compare_long() +{ + assert(compare_to(long.max, long.min) == 1); + assert(compare_to(long.min, long.max) == -1); + assert(compare_to(long.min, long.min) == 0); +} + +fn void compare_ulong() +{ + assert(compare_to(ulong.max, ulong.min) == 1); + assert(compare_to(ulong.min, ulong.max) == -1); + assert(compare_to(ulong.min, ulong.min) == 0); +} + +fn void compare_int128() +{ + assert(compare_to(int128.max, int128.min) == 1); + assert(compare_to(int128.min, int128.max) == -1); + assert(compare_to(int128.min, int128.min) == 0); +} diff --git a/test/unit7/stdlib/core/dstring.c3 b/test/unit7/stdlib/core/dstring.c3 new file mode 100644 index 000000000..f30ee5543 --- /dev/null +++ b/test/unit7/stdlib/core/dstring.c3 @@ -0,0 +1,277 @@ +module std::core::dstring2 @test; + +const TEST_STRING = "hello world"; + +fn void test_replace() +{ + DString hello = dstring::new(mem, "Hello world where are you? Are you here too?"); + defer hello.free(); + hello.replace("u", "ooo"); + assert(hello.str_view() == "Hello world where are yoooo? Are yoooo here too?"); +} + +fn void test_reverse() +{ + DString a; + a.append("abcd"); + a.reverse(); + assert(a.str_view() == "dcba"); + a.reverse(); + assert(a.str_view() == "abcd"); + a.append("e"); + assert(a.str_view() == "abcde"); + a.reverse(); + assert(a.str_view() == "edcba"); + a.free(); +} + +fn void test_delete() +{ + { + DString d; + d.append("Hello cruel world."); + d.delete_range(5, 10); + assert(d.str_view() == "Hello world."); + d.free(); + } + DString d; + d.append("Hello cruel world."); + d.delete(0, 2); + assert(d.str_view() == "llo cruel world."); + d.delete(14, 2); + assert(d.str_view() == "llo cruel worl"); + d.delete(7); + assert(d.str_view() == "llo crul worl"); + d.delete(2, 0); + assert(d.str_view() == "llo crul worl"); + d.delete(0, 1); + assert(d.str_view() == "lo crul worl"); + d.free(); +} + +fn void test_append() +{ + DString str = dstring::new(mem, TEST_STRING); + defer str.free(); + String s; + + assert(str.len() == TEST_STRING.len); + assert(str.capacity() == 16); + + s = str.str_view(); + assert(s == TEST_STRING, "got '%s'; want '%s'", s, TEST_STRING); + ZString zs; + zs = str.zstr_view(); + assert(s.ptr[s.len] == 0, "got '%c'; want 0", s.ptr[s.len]); + + str.chop(5); + s = str.str_view(); + assert(s == TEST_STRING[:5], "got '%s'; want '%s'", s, TEST_STRING[:5]); + + str.append(TEST_STRING[5..]); + s = str.str_view(); + assert(s == TEST_STRING, "got '%s'; want '%s'", s, TEST_STRING); + + str.append_char('x'); + s = str.str_view(); + assert(s[^1] == 'x', "got '%c'; want 'x'", s[^1]); + + str.append('y'); + s = str.str_view(); + assert(s[^1] == 'y', "got '%c'; want 'y'", s[^1]); + + str.set(0, 'z'); + s = str.str_view(); + assert(s[0] == 'z', "got '%c'; want 'z'", s[0]); + + str.clear(); + assert(str.len() == 0); + + str.append_char32('é'); + s = str.str_view(); + assert(s == "é", "got '%s'; want 'é'", s); + + str.append_utf32({ 'è', 'à' }); + s = str.str_view(); + assert(s == "éèà", "got '%s'; want 'éèà'", s); + str.clear(); + + str.append_chars("foo"); + s = str.str_view(); + assert(s == "foo", "got '%s'; want 'foo'", s); + str.clear(); + + str.append_repeat('x', 3); + s = str.str_view(); + assert(s == "xxx", "got '%s'; want 'xxx'", s); + + DString str2 = dstring::new(mem, "yyy"); + defer str2.free(); + DString str3 = str.concat(mem, str2); + defer str3.free(); + s = str3.str_view(); + assert(s == "xxxyyy", "got '%s'; want 'xxxyyy'", s); + + str3.clear(); + str3.append_string(str2); + s = str3.str_view(); + assert(s == "yyy", "got '%s'; want 'yyy'", s); +} + +fn void test_print() +{ + DString str = dstring::new(mem, ""); + defer str.free(); + String s; + + str.appendf("_%s_", "foo"); + s = str.str_view(); + assert(s == "_foo_", "got '%s'; want '_foo_'", s); + + str.clear(); + str.appendfn("_%s_", "foo"); + s = str.str_view(); + assert(s == "_foo_\n", "got '%s'; want '_foo_\n'", s); +} + +fn void test_copy() +{ + DString str = dstring::new(mem, TEST_STRING); + defer str.free(); + String s; + + DString str2 = str.copy(mem); + defer str2.free(); + s = str2.str_view(); + assert(s == TEST_STRING, "got '%s'; want '%s'", s, TEST_STRING); +} + +fn void test_cmp() +{ + DString str = dstring::new(mem, TEST_STRING); + defer str.free(); + + DString str2 = dstring::new(mem, TEST_STRING); + defer str2.free(); + + assert(str.equals(str2)); + + str2.clear(); + str2.append("hello you.."); + assert(str.len() == str2.len()); + assert(!str.less(str2)); +} + +fn void test_join() +{ + DString str = dstring::join(mem, {"hello", "world"}, " "); + defer str.free(); + + String s = str.str_view(); + assert(s == TEST_STRING, "got '%s'; want '%s'", s, TEST_STRING); +} + +fn void test_insert_at() +{ + DString str = dstring::tnew(" world"); + String s; + + str.insert_at(0, ""); + s = str.str_view(); + assert(s == " world", "got '%s'; want ' world'", s); + + str.insert_at(0, "hello"); + s = str.str_view(); + assert(s == "hello world", "got '%s'; want 'hello world'", s); + + str.insert_at(5, " shiny"); + s = str.str_view(); + assert(s == "hello shiny world", "got '%s'; want 'hello shiny world'", s); + + str.insert_at(0, '['); + s = str.str_view(); + assert(s == "[hello shiny world", "got '%s'; want '[hello shiny world'", s); + + str.insert_at(18, ']'); + s = str.str_view(); + assert(s == "[hello shiny world]", "got '%s'; want 'hello shiny world]'", s); + + str.insert_at(0, 'ꫩ'); + s = str.str_view(); + assert(s == "ꫩ[hello shiny world]", "got '%s'; want 'ꫩ[hello shiny world]'", s); + + // ꫩ is 3 bytes long + str.insert_utf32_at(4, {'寃', 't', 'e', 'x', 't', '¥'}); + s = str.str_view(); + assert(s == "ꫩ[寃text¥hello shiny world]", "got '%s'; want 'ꫩ[寃text¥hello shiny world]'", s); +} + +fn void test_insert_at_overlaps() +{ + DString str = dstring::tnew("abc"); + String s; + String v; + + str.insert_at(0, "bc"); + s = str.str_view(); + assert(s == "bcabc", "got '%s'; want 'bcabc'", s); + + // Inserted string is unchanged. + str.chop(0); + str.append("abc"); + v = str.str_view(); + str.insert_at(0, v); + s = str.str_view(); + assert(s == "abc", "got '%s'; want 'abc'", s); + + // Inserted string is part of the tail. + str.chop(0); + str.append("abc"); + v = str.str_view()[1..]; + assert(v == "bc"); + str.insert_at(0, v); + s = str.str_view(); + assert(s == "bcabc", "got '%s'; want 'bcabc'", s); + + // Inserted string is part of the head. + str.chop(0); + str.append("abc"); + v = str.str_view()[1..]; + str.insert_at(2, v); + s = str.str_view(); + assert(s == "abbcc", "got '%s'; want 'abbcc'", s); + + str.chop(0); + str.append("abcdef"); + v = str.str_view()[3..]; + assert(v == "def"); + str.insert_at(0, v); + str.chop(3); + s = str.str_view(); + assert(s == "def", "got '%s'; want 'def'", s); +} + +fn void test_char_at() +{ + DString str = dstring::new(mem, "hello"); + defer str.free(); + + char c = str.char_at(1); + assert(c == 'e'); + + char c_with_operator = str[4]; + assert(c_with_operator == 'o'); +} + +fn void test_operators() +{ + DString str = dstring::new(mem, "hello"); + defer str.free(); + + str[0] = 'p'; + assert(str.str_view() == "pello"); + + char* c = &str[1]; + assert(*c == 'e'); +} + diff --git a/test/unit7/stdlib/core/formatter.c3 b/test/unit7/stdlib/core/formatter.c3 new file mode 100644 index 000000000..6fd743c2e --- /dev/null +++ b/test/unit7/stdlib/core/formatter.c3 @@ -0,0 +1,20 @@ +module std::core::formatter_test; + +import std; + +struct Foo (Printable) { int a; } +fn usz! Foo.to_format(&self, Formatter *f) @dynamic +{ + return f.printf("Foo[%d]", self.a); +} + +fn void test_ref() @test +{ + Foo* f = &&(Foo){ 8 }; + Foo* f0 = null; + int* a = (void*)(uptr)0x40; + int* b = null; + String s = string::format(mem, "%s %s %s %s %s", a, b, f0, f, *f); + defer free(s); + assert(s == "0x40 0x0 (null) Foo[8] Foo[8]"); +} diff --git a/test/unit7/stdlib/core/mem_allocator.c3 b/test/unit7/stdlib/core/mem_allocator.c3 new file mode 100644 index 000000000..24f93ad65 --- /dev/null +++ b/test/unit7/stdlib/core/mem_allocator.c3 @@ -0,0 +1,25 @@ +module std::core::allocator_test; + +import std; + +struct Foo +{ + int x; + int y; +} + +struct Bar +{ + int x; + int y; + List{Foo} foos; +} + +fn void test_new_aligned_compiles() @test +{ + Bar* bar2 = allocator::new_aligned(allocator::heap(), Bar)!!; + allocator::free_aligned(allocator::heap(), bar2); + + Bar* bar = allocator::new_aligned(allocator::heap(), Bar, {.x = 1, .y = 2, .foos = {}})!!; + allocator::free_aligned(allocator::heap(), bar); +} diff --git a/test/unit7/stdlib/core/runtime.c3 b/test/unit7/stdlib/core/runtime.c3 new file mode 100644 index 000000000..8a6245e02 --- /dev/null +++ b/test/unit7/stdlib/core/runtime.c3 @@ -0,0 +1,37 @@ +module std::core::runtime_test; +import std::sort; + +fn void cmp_unit() @test +{ + TestUnit[] list = { + { .name = "http::url_test::url_query" }, + { .name = "http::url_test::url_init" }, + { .name = "http::url_test::url_decode" }, + { .name = "text_test::test_render_notag" }, + { .name = "text_test::test_render_tag1" }, + { .name = "text_test::test_render_template_iter" }, + { .name = "http::header_test::header_scan" }, + { .name = "http::header_test::header" }, + { .name = "stringmap_test::test_map" }, + { .name = "text_test::test_render_template" }, + }; + quicksort(list, &runtime::cmp_test_unit); + + String[] want = { + "http::header_test::header", + "http::header_test::header_scan", + "http::url_test::url_decode", + "http::url_test::url_init", + "http::url_test::url_query", + "stringmap_test::test_map", + "text_test::test_render_notag", + "text_test::test_render_tag1", + "text_test::test_render_template", + "text_test::test_render_template_iter", + }; + assert(list.len == want.len); + foreach (i, l : list) + { + assert(l.name == want[i], "got %s; want %s", l.name, want[i]); + } +} \ No newline at end of file diff --git a/test/unit7/stdlib/core/string.c3 b/test/unit7/stdlib/core/string.c3 new file mode 100644 index 000000000..64795551d --- /dev/null +++ b/test/unit7/stdlib/core/string.c3 @@ -0,0 +1,208 @@ +module std::core::string::tests @test; + +fn void test_starts_with() +{ + String s = "ofke"; + assert(s.starts_with("of")); + assert(s.starts_with("ofke")); + assert(!s.starts_with("ofkes")); + assert(!s.starts_with("ofkf")); + s = ""; + assert(s.starts_with("")); + assert(!s.starts_with("o")); +} + +fn void test_print_null() +{ + ZString z; + int* y; + ZString w = "hello"; + String s = string::format(mem, "%s %s %s", z, w, y); + defer free(s); + assert(s == "(null) hello 0x0"); +} + +fn void test_strip() +{ + String s = "ofke"; + assert(s.strip("of") == "ke"); + assert(s.strip("ofke") == ""); + assert(s.strip("ofkes") == "ofke"); + assert(s.strip("ofkf") == "ofke"); + assert(s.strip("") == "ofke"); + s = ""; + assert(s.strip("") == ""); + assert(s.strip("o") == ""); +} + +fn void test_strip_end() +{ + String s = "ofke"; + assert(s.strip_end("ke") == "of"); + assert(s.strip_end("ofke") == ""); + assert(s.strip_end("ofkes") == "ofke"); + assert(s.strip_end("ofkf") == "ofke"); + assert(s.strip_end("") == "ofke"); + s = ""; + assert(s.strip_end("") == ""); + assert(s.strip_end("o") == ""); +} + +fn void test_ends_with() +{ + String s = "ofke"; + assert(s.ends_with("ke")); + assert(s.ends_with("ofke")); + assert(!s.ends_with("ofkes")); + assert(!s.ends_with("ofkf")); + s = ""; + assert(s.ends_with("")); + assert(!s.ends_with("e")); +} + +fn void test_trim() +{ + String s = " \t\nabc "; + assert(s.trim() == "abc"); + assert("\n\t".trim() == ""); + assert(" \n\tok".trim() == "ok"); + assert("!! \n\t ".trim() == "!!"); + assert(s.trim("c \t") == "\nab"); + assert("".trim() == ""); + + +} + +fn void test_trim_left() +{ + String s = " \t\nabc "; + assert(s.trim_left() == "abc "); + assert("\n\t".trim_left() == ""); + assert(" \n\tok".trim_left() == "ok"); + assert("!! \n\t ".trim_left() == "!! \n\t "); + assert("".trim_left() == ""); +} + +fn void test_trim_right() +{ + String s = " \t\nabc "; + assert(s.trim_right() == " \t\nabc"); + assert("\n\t".trim_right() == ""); + assert(" \n\tok".trim_right() == " \n\tok"); + assert("!! \n\t ".trim_right() == "!!"); + assert("".trim_right() == ""); +} + +fn void test_split() +{ + String test = "abc|b||c|"; + String[] strings = test.split(mem, "|"); + assert(strings.len == 5); + assert(strings[0] == "abc"); + assert(strings[1] == "b"); + assert(strings[2] == ""); + assert(strings[3] == "c"); + assert(strings[4] == ""); + free(strings); + strings = test.split(mem, "|", 2); + assert(strings.len == 2); + assert(strings[0] == "abc"); + assert(strings[1] == "b||c|"); + free(strings); +} + +fn void test_split_skip_empty() +{ + String test = "abc|b||c|"; + String[] strings = test.split(mem, "|", skip_empty: true); + assert(strings.len == 3); + assert(strings[0] == "abc"); + assert(strings[1] == "b"); + assert(strings[2] == "c"); + free(strings); + strings = test.split(mem, "|", 2, skip_empty: true); + assert(strings.len == 2); + assert(strings[0] == "abc"); + assert(strings[1] == "b||c|"); + free(strings); +} + +fn void test_split_to_buffer_skip_empty() +{ + String[10] buffer; + String test = "abc|b||c|"; + String[] strings = test.split_to_buffer("|", &buffer, skip_empty: true)!!; + assert(strings.len == 3); + assert(strings[0] == "abc"); + assert(strings[1] == "b"); + assert(strings[2] == "c"); + strings = test.split(mem, "|", 2, skip_empty: true); + assert(strings.len == 2); + assert(strings[0] == "abc"); + assert(strings[1] == "b||c|"); + free(strings); +} + +fn void test_split_to_buffer() +{ + String[5] b; + String test = "abc|b||c|"; + String[] strings = test.split_to_buffer("|", &b)!!; + assert(strings.len == 5); + assert(strings[0] == "abc"); + assert(strings[1] == "b"); + assert(strings[2] == ""); + assert(strings[3] == "c"); + assert(strings[4] == ""); + String[4] c; + assert(@catch(test.split_to_buffer("|", &c)) == SplitResult.BUFFER_EXCEEDED); + strings = test.split(mem, "|", 2); + assert(strings.len == 2); + assert(strings[0] == "abc"); + assert(strings[1] == "b||c|"); + free(strings); +} + +fn void test_index_of() +{ + String test = "hello world hello"; + assert(test.index_of("o")!! == 4); + assert(test.index_of("ll")!! == 2); + assert(test.index_of(" hello")!! == 11); + assert(@catch(test.index_of("wi"))); +} + +fn void test_rindex_of() +{ + String test = "hello world hello"; + assert(test.rindex_of("o")!! == 16); + assert(test.rindex_of("ll")!! == 14); + assert(test.rindex_of("he")!! == 12); + assert(test.rindex_of("world")!! == 6); + assert(test.rindex_of("hello ")!! == 0); + assert(@catch(test.rindex_of("wi"))); +} + +fn void test_index_of_char() +{ + String test = "hello world hello"; + assert(test.index_of_char('o')!! == 4); + assert(test.index_of_char('l')!! == 2); + assert(test.index_of_char('h')!! == 0); + assert(@catch(test.index_of_char('x'))); +} + +fn void test_rindex_of_char() +{ + String test = "hello world hello"; + assert(test.rindex_of_char('o')!! == 16); + assert(test.rindex_of_char('l')!! == 15); + assert(test.rindex_of_char('h')!! == 12); + assert(@catch(test.index_of_char('x'))); +} + +fn void test_hex_conversion() +{ + assert("0x123aCd".to_long()!! == 0x123acd); + assert("123acD".to_long(16)!! == 0x123acd); +} diff --git a/test/unit7/stdlib/core/string_iterator.c3 b/test/unit7/stdlib/core/string_iterator.c3 new file mode 100644 index 000000000..4c287539a --- /dev/null +++ b/test/unit7/stdlib/core/string_iterator.c3 @@ -0,0 +1,35 @@ +module std::core::test::string_iterator::tests @test; + +fn void test_at_start() +{ + String test = "abcd"; + StringIterator iterator = test.iterator(); + assert(iterator.get()!! == 'a'); + assert(iterator.peek()!! == 'a'); + iterator.next()!!; + assert(iterator.next()!! == 'b'); + assert(iterator.has_next()); +} + + +fn void test_general() +{ + String test = "åƦs1"; + StringIterator iterator = test.iterator(); + assert(iterator.get()!! == 'å'); + iterator.next()!!; + assert(iterator.peek()!! == 'Ʀ'); + assert(iterator.next()!! == 'Ʀ'); + iterator.reset(); + assert(iterator.current == 0); +} + +fn void test_end() +{ + String test = "åƦ"; + StringIterator iterator = test.iterator(); + assert(@ok(iterator.next())); + assert(iterator.peek()!! == 'Ʀ'); + assert(@ok(iterator.next())); + assert(@catch(iterator.next())); +} diff --git a/test/unit7/stdlib/core/test_test.c3 b/test/unit7/stdlib/core/test_test.c3 new file mode 100644 index 000000000..afb584fe1 --- /dev/null +++ b/test/unit7/stdlib/core/test_test.c3 @@ -0,0 +1,332 @@ +module test::std::core::test @test; +import std::core::runtime @public; +import std::core::builtin; +import std::io; + +struct TestState +{ + int n_runs; + int n_fails; + bool expected_fail; + + // NOTE: we must wrap setup/teardown functions to hide them from module @test runner + TestFn setup_fn; + TestFn teardown_fn; + PanicFn old_panic; // original test panic, use it when it's really fails + PanicFn panic_mock_fn; // mock panic, for testing the test:: failed + void* buf; +} + +TestState state = +{ + .setup_fn = fn void() + { + state.n_runs++; + state.n_fails = 0; + + assert (runtime::test_context.assert_print_backtrace); + assert (builtin::panic != state.panic_mock_fn, "missing finalization of panic"); + state.buf = mem::alloc(int); + + state.old_panic = builtin::panic; + builtin::panic = state.panic_mock_fn; + }, + .teardown_fn = fn void() + { + builtin::panic = state.old_panic; + + assert(state.n_runs > 0); + + if (state.expected_fail) + { + assert(state.n_fails > 0, "test case expected to fail, but it's not"); + } + state.n_fails = 0; + state.expected_fail = false; + state.n_runs = 0; + mem::free(state.buf); + }, + .panic_mock_fn = fn void (String message, String file, String function, uint line) + { + if (runtime::test_context.is_in_panic) return; + if (runtime::test_context.assert_print_backtrace) + { + $if env::NATIVE_STACKTRACE: + builtin::print_backtrace(message, 0); + $else + io::printfn("No print_backtrace() supported by this platform"); + $endif + } + runtime::test_context.assert_print_backtrace = true; + if (state.expected_fail) + { + state.n_fails++; + } + else + { + builtin::panic = state.old_panic; + state.old_panic(message, file, function, line); + } + runtime::test_context.is_in_panic = false; + } +}; + + +fn void test_eq() +{ + test::eq(1, 1); + test::eq(true, true); + test::eq(1.31, 1.31); + test::eq("foo", "foo"); +} + +fn void test_almost_equal() +{ + test::eq_approx(1, 1); + test::eq_approx(1.31, 1.31); + test::eq_approx(1.31f, 1.31f); + test::eq_approx(double.nan, double.nan); + test::eq_approx(float.nan, float.nan); + test::eq_approx(1.31, 1.31, delta: 0.01); + test::eq_approx(1.311, 1.312, delta: 0.01); + test::eq_approx(1.311, 1.312, places: 2); + // 7 decimal places are default + test::eq_approx(1.00000001, 1.00000000); +} + + +fn void test_almost_equal_fails() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + // 7 decimal places are default + test::eq_approx(1.0000001, 1.00000000); +} + +fn void test_almost_equal_fails_nan() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::eq_approx(1.0000001, double.nan); +} + +fn void test_almost_equal_fails_nan2() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::eq_approx(double.nan, 1); +} + +fn void test_almost_equal_fails_equal_nan_false() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::eq_approx(double.nan, double.nan, equal_nan: false); +} + +fn void setup_teardown() +{ + state.n_runs = 0; // just in case of previous test failed + test::@setup(state.setup_fn, state.teardown_fn); + + test::eq(state.n_runs, 1); + test::eq(state.n_fails, 0); + test::eq(state.expected_fail, false); +} + +fn void setup_no_teardown() +{ + test::@setup(state.setup_fn); + + test::eq(state.n_runs, 1); + test::eq(state.n_fails, 0); + test::eq(state.expected_fail, false); + + mem::free(state.buf); + + // WARNING: reverting back original panic func + builtin::panic = state.old_panic; +} + +fn void expected_fail() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::eq(state.n_fails, 0); + test::eq(2, 1); // this fails, and we test it + test::eq(state.n_fails, 1); +} + +fn void test_neq() +{ + test::ne(2, 1); + test::ne(false, true); + test::ne(1.32, 1.31); + test::ne("foo", "bar"); +} + +fn void test_neq_fails() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::ne(1, 1); +} + +fn void test_gt() +{ + test::gt(2, 1); + test::gt(true, false); + test::gt(1.32, 1.31); +} + +fn void test_gt_fails_when_equal() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::gt(2, 2); +} + +fn void test_gt_fails_when_less() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::gt(1, 2); +} + + +fn void test_gte() +{ + test::ge(2, 1); + test::ge(true, false); + test::ge(1.32, 1.31); + test::ge(2, 2); + test::ge(true, true); + test::ge(1.32, 1.32); +} + +fn void test_gte_fails_when_less() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::ge(1, 2); +} + +fn void test_lt() +{ + test::lt(1, 2); + test::lt(false, true); + test::lt(1.31, 1.32); +} + +fn void test_lt_fails_when_equal() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::lt(2, 2); +} + +fn void test_lt_fails_when_greater() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::lt(2, 1); +} + +fn void test_lte() +{ + test::le(1, 2); + test::le(false, true); + test::le(1.31, 1.32); + test::le(2, 2); + test::le(true, true); + test::le(1.32, 1.32); +} + +fn void test_lte_fails_when_greater() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::le(2, 1); +} + +fn void test_check(){ + test::@check(1 == 1); + test::@check(1.2 == 1.2, "1 == 1"); + test::@check(true == true, "1 == 1"); + test::@check("foo" == "foo", "2 == %d", 1 ); +} + +fn void test_check_fails() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::@check(2 == 1, "2 == %d", 1 ); +} + +fn void test_check_fails_no_info() +{ + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::@check(2 == 1); +} + +def TestIntFn = fn int! (int a, int b); +def TestFailFn = fn void! (bool to_fail); + +fault MyFault +{ + FOO, +} + +fn void test_error() +{ + TestFailFn ffail_void = fn void!(bool to_fail) + { + if (to_fail) return IoError.FILE_NOT_FOUND?; + }; + TestIntFn ffail_int = fn int! (int a, int b) + { + if (b == 0) return IoError.FILE_NOT_FOUND?; + return a / b; + }; + test::@setup(state.setup_fn, state.teardown_fn); + + test::@error(ffail_void(true), IoError.FILE_NOT_FOUND); + test::@error(ffail_int(1, 0), IoError.FILE_NOT_FOUND); +} + +fn void test_error_not_raised() +{ + TestIntFn ffail_int = fn int! (int a, int b) { + if (b == 0) return IoError.FILE_NOT_FOUND?; + return a / b; + }; + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + test::@error(ffail_int(1, 1), IoError.FILE_NOT_FOUND); +} + +fn void test_error_wrong_error_expected() +{ + TestIntFn ffail_int = fn int! (int a, int b) { + if (b == 0) return IoError.BUSY?; + return a / b; + }; + test::@setup(state.setup_fn, state.teardown_fn); + state.expected_fail = true; + + test::@error(ffail_int(1, 0), IoError.FILE_NOT_FOUND); +} + +fn void test_std_out_hijack() +{ + io::print("print: aldsjalsdjlasjdlja\n"); + io::printf("printf: aldsjalsdjlasjdlja\n"); + io::eprint("eprint: aldsjalsdjlasjdlja\n"); + io::eprintfn("eprintfn: aldsjalsdjlasjdlja\n"); + io::fprint(io::stdout(), "fprint: stdout aldsjalsdjlasjdlja\n")!!; + io::fprint(io::stderr(), "fprint: stderr aldsjalsdjlasjdlja\n")!!; + io::fprintf(io::stderr(), "fprintf: stderr aldsjalsdjlasjdlja\n")!!; + io::fprintf(io::stderr(), "fprintfn: stderr aldsjalsdjlasjdlja\n")!!; + test::eq(true, true); +} diff --git a/test/unit7/stdlib/core/values.c3 b/test/unit7/stdlib/core/values.c3 new file mode 100644 index 000000000..b4644dce5 --- /dev/null +++ b/test/unit7/stdlib/core/values.c3 @@ -0,0 +1,9 @@ +module std::core::values @test; + +fn void test_select() +{ + const int X = @select(true, 1, "Hello"); + const String Y = @select(false, 1, "Hello"); + test::eq(X, 1); + test::eq(Y, "Hello"); +} \ No newline at end of file diff --git a/test/unit7/stdlib/crypto/rc4.c3 b/test/unit7/stdlib/crypto/rc4.c3 new file mode 100644 index 000000000..5615f2bb8 --- /dev/null +++ b/test/unit7/stdlib/crypto/rc4.c3 @@ -0,0 +1,16 @@ +import std::crypto; +import std::io; + +fn void rc_crypt() @test +{ + Rc4 rc; + rc.init(&&x"63727970746969"); + char[200] x; + String text = "The quick brown fox jumps over the lazy dog."; + rc.crypt(text, &x); + char[?] res = x'2ac2fecdd8fbb84638e3a4 + 820eb205cc8e29c28b9d5d + 6b2ef974f311964971c90e + 8b9ca16467ef2dc6fc3520'; + assert(res[:text.len] == x[:text.len]); +} \ No newline at end of file diff --git a/test/unit7/stdlib/encoding/base32.c3 b/test/unit7/stdlib/encoding/base32.c3 new file mode 100644 index 000000000..fe6b3b960 --- /dev/null +++ b/test/unit7/stdlib/encoding/base32.c3 @@ -0,0 +1,108 @@ +module encoding::base32 @test; +import std::encoding::base32; + +// https://www.rfc-editor.org/rfc/rfc4648#section-10 + +struct TestCase +{ + char[] dec; + char[] enc; +} + +TestCase[?] std_tests = { + { "", "" }, + { "f", "MY======" }, + { "fo", "MZXQ====" }, + { "foo", "MZXW6===" }, + { "foob", "MZXW6YQ=" }, + { "fooba", "MZXW6YTB" }, + { "foobar", "MZXW6YTBOI======" }, +}; + +TestCase[?] hex_tests = { + { "", "" }, + { "f", "CO======" }, + { "fo", "CPNG====" }, + { "foo", "CPNMU===" }, + { "foob", "CPNMUOG=" }, + { "fooba", "CPNMUOJ1" }, + { "foobar", "CPNMUOJ1E8======" }, +}; + +macro encode_tests(tests, alphabet, padding) +{ + foreach (t : tests) + { + char[64] buf; + usz n = base32::encode_len(t.dec.len, padding); + base32::encode_buffer(t.dec, buf[:n], padding, alphabet); + + char[] want = t.enc; + usz! pad_idx = array::index_of(want, '='); + if (try pad_idx && !padding) + { + want = want[:pad_idx]; + } + + assert(buf[:n] == want, "got: %s, want: %s", + (String)buf[:n], (String)want); + } +} + +fn void encode() +{ + encode_tests(std_tests, &base32::STANDARD, '='); + encode_tests(hex_tests, &base32::HEX, '='); +} + +fn void encode_nopadding() +{ + encode_tests(std_tests, &base32::STANDARD, base32::NO_PAD); + encode_tests(hex_tests, &base32::HEX, base32::NO_PAD); +} + +macro decode_tests(tests, alphabet, padding) +{ + foreach (t : tests) + { + char[] input = t.enc[..]; + usz! pad_idx = array::index_of(input, '='); + if (try pad_idx && !padding) + { + input = input[:pad_idx]; + } + + char[64] buf; + usz n = base32::decode_len(input.len, padding); + char[] buf2 = base32::decode_buffer(input, buf[:n], padding, alphabet)!!; + + assert(buf2 == t.dec, "got: %s, want: %s", buf2, (String)t.dec); + } +} + +fn void decode() +{ + decode_tests(std_tests, &base32::STANDARD, '='); + decode_tests(hex_tests, &base32::HEX, '='); +} + +fn void decode_nopadding() +{ + decode_tests(std_tests, &base32::STANDARD, base32::NO_PAD); + decode_tests(hex_tests, &base32::HEX, base32::NO_PAD); +} + +fn void base32_api() +{ + @pool() + { + foreach (t : std_tests) + { + String got = base32::encode_temp(t.dec)!!; + assert(got == t.enc, "got: %s, want: %s", got, t.enc); + + char[] got_chars = base32::decode_temp(t.enc)!!; + assert(got_chars == t.dec, "got: %s, want: %s", got_chars, t.dec); + } + }; +} diff --git a/test/unit7/stdlib/encoding/base64.c3 b/test/unit7/stdlib/encoding/base64.c3 new file mode 100644 index 000000000..211fff071 --- /dev/null +++ b/test/unit7/stdlib/encoding/base64.c3 @@ -0,0 +1,122 @@ +module encoding::base64_test @test; +import std::encoding::base64; + +// https://www.rfc-editor.org/rfc/rfc4648#section-10 + +struct TestCase +{ + char[] in; + char[] out; +} +import std; + +fn void encode() +{ + TestCase[] tcases = { + { "", "" }, + { "f", "Zg==" }, + { "fo", "Zm8=" }, + { "foo", "Zm9v" }, + { "foob", "Zm9vYg==" }, + { "fooba", "Zm9vYmE=" }, + { "foobar", "Zm9vYmFy" }, + { "test", "dGVzdA==" }, + }; + foreach (tc : tcases) + { + @pool() + { + + usz n = base64::encode_len(tc.in.len, base64::DEFAULT_PAD); + char[64] buf; + char[] res = base64::encode_buffer(tc.in, buf[:n]); + assert(res == tc.out); + }; + } +} + +fn void encode_nopadding() +{ + TestCase[] tcases = { + { "", "" }, + { "f", "Zg" }, + { "fo", "Zm8" }, + { "foo", "Zm9v" }, + { "foob", "Zm9vYg" }, + { "fooba", "Zm9vYmE" }, + { "foobar", "Zm9vYmFy" }, + { "test", "dGVzdA" }, + }; + foreach (tc : tcases) + { + usz n = base64::encode_len(tc.in.len, base64::NO_PAD); + char[64] buf; + base64::encode_buffer(tc.in, buf[:n], padding: base64::NO_PAD); + assert(buf[:n] == tc.out); + } +} + +fn void decode() +{ + TestCase[] tcases = { + { "", "" }, + { "Zg==", "f" }, + { "Zm8=", "fo" }, + { "Zm9v", "foo" }, + { "Zm9vYg==", "foob" }, + { "Zm9vYmE=", "fooba" }, + { "Zm9vYmFy", "foobar" }, + { "Zm9vYmFy", "foobar" }, + { "dGVzdA==", "test" }, + }; + foreach (tc : tcases) + { + usz n = base64::decode_len(tc.in.len, base64::DEFAULT_PAD)!!; + char[64] buf; + char[] res = base64::decode_buffer(tc.in, buf[:n])!!; + assert(res == tc.out); + } +} + +fn void decode_nopadding() +{ + TestCase[] tcases = { + { "", "" }, + { "Zg", "f" }, + { "Zm8", "fo" }, + { "Zm9v", "foo" }, + { "Zm9vYg", "foob" }, + { "Zm9vYmE", "fooba" }, + { "Zm9vYmFy", "foobar" }, + { "dGVzdA", "test" }, + }; + foreach (tc : tcases) + { + usz n = base64::decode_len(tc.in.len, base64::NO_PAD)!!; + char[64] buf; + char[] res = base64::decode_buffer(tc.in, buf[:n], base64::NO_PAD)!!; + assert(res == tc.out); + } +} + +fn void urlencode() { + TestCase[] tcases = { + { x"14fb9c03d97e", "FPucA9l-"}, + }; + + @pool() + { + usz n; + char[] got; + char[64] buf; + foreach (t : tcases) + { + char[] res = base64::encode_buffer(t.in, buf[..], alphabet: &base64::URL); + assert (res == t.out, "got: %s, want: %s", (String)res, (String)t.out); + + res = base64::decode_buffer(t.out, buf[..], alphabet: &base64::URL)!!; + assert (res == t.in, "got: %s, want: %s", (String)res, (String)t.in); + + } + }; +} diff --git a/test/unit7/stdlib/encoding/csv.c3 b/test/unit7/stdlib/encoding/csv.c3 new file mode 100644 index 000000000..504804101 --- /dev/null +++ b/test/unit7/stdlib/encoding/csv.c3 @@ -0,0 +1,68 @@ +module csv_test @test; +import std::encoding::csv; +import std::io; + +struct TestCase { + String input; + String[] want; + String sep; +} + +fn void csv_each_row() +{ + TestCase[] tests = { + { + "aaa,bbb,ccc", + {"aaa","bbb","ccc"}, + "," + }, + { + "aaa;bbb;ccc", + {"aaa", "bbb", "ccc"}, + ";" + }, + { + "aaa,bbb,ccc\n111,222,333", + {"aaa", "bbb", "ccc", "111", "222", "333"}, + "," + }, + { + "aaa , bbb\t ,ccc\r\n 111,222,333\r\n", + {"aaa ", " bbb\t ", "ccc", " 111", "222", "333"}, + "," + }, + }; + foreach (t : tests) { + String[] want = t.want; + + CsvReader r; + r.init((ByteReader){}.init(t.input), t.sep); + r.@each_row(; String[] row) { + foreach (i, s : row) { + assert(want.len > 0, + "more records than we want"); + assert(s == want[0], "columns do not match; " + "got: '%s', want: '%s'", s, want[0]); + want = want[1..]; + } + }; + assert(want.len == 0, "not enough records found"); + } +} + +fn void csv_row() +{ + TestCase t = { + "aaa,bbb,ccc", + {"aaa", "bbb", "ccc"}, + "," + }; + CsvReader r; + r.init((ByteReader){}.init(t.input), t.sep); + CsvRow row = r.tread_row()!!; + assert(row.list.len == t.want.len, "not enough records found"); + for (int i = 0; i < row.list.len; i++) { + assert(row.list[i] == t.want[i],"columns do not match; " + "got: '%s', want: '%s'", row.list[i], t.want[i]); + } +} diff --git a/test/unit7/stdlib/encoding/hex.c3 b/test/unit7/stdlib/encoding/hex.c3 new file mode 100644 index 000000000..bac6eac31 --- /dev/null +++ b/test/unit7/stdlib/encoding/hex.c3 @@ -0,0 +1,49 @@ +module encoding::hex @test; +import std::encoding::hex; + +struct TestCase +{ + char[] dec; + char[] enc; +} + +TestCase[] tests = { + {"", ""}, + {{'g'}, "67"}, + {{0,1,2,3,4,5,6,7}, "0001020304050607"}, + {{8,9,10,11,12,13,14,15}, "08090a0b0c0d0e0f"}, + {{0xf0, 0xf1, 0xf2, 0xf3}, "f0f1f2f3"}, + {{0xe3, 0xa1}, "e3a1"}, + {{0xe3, 0xa1}, "E3A1"}, +}; + +fn void encode() +{ + usz n; + char[64] buf; + foreach (t : tests) + { + n = hex::encode_bytes(t.dec, buf[..]); + String want = ((String)t.enc).to_lower_tcopy(); + assert(want == buf[:n], "encode failed: got: %s, want: %s", buf[:n], want); + @pool() + { + assert(want == hex::tencode(t.dec)); + }; + } +} + +fn void decode() +{ + usz n; + char[64] buf; + foreach (t : tests) + { + n = hex::decode_bytes(t.enc, buf[..])!!; + assert(t.dec == buf[:n], "decode failed: got: %s, want: %s", buf[:n], t.dec); + @pool() + { + assert(t.dec == hex::tdecode(t.enc)!!); + }; + } +} diff --git a/test/unit7/stdlib/encoding/json.c3 b/test/unit7/stdlib/encoding/json.c3 new file mode 100644 index 000000000..38939f439 --- /dev/null +++ b/test/unit7/stdlib/encoding/json.c3 @@ -0,0 +1,53 @@ +module json_test @test; +import std::collections::object; +import std::io; +import std::encoding::json; + +fn void simple_test() +{ + ByteReader reader; + reader.init(`{ "b": 123, "c": [ { "d": 66 }, null, "hello\tworld", false, { "id": "xyz" } ] }`); + Object* o = json::parse(mem, &reader)!!; + defer o.free(); + assert(o.get_int("b")!! == 123); + assert(o.get("c").get_len()!! == 5); + assert(o.get("c").get_at(0).get_int("d")!! == 66); + assert(o.get("c").get_at(1).is_null()!!); + assert(o.get("c").get_string_at(2)!! == "hello\tworld"); + assert(o.get("c").get_bool_at(3)!! == false); + assert(o.get("c").get_at(4).get_string("id")!! == "xyz"); +} + +fn void simple_test2() +{ + ByteReader reader; + reader.init(`{"jsonrpc":"2.0","id":null,"method":"initialize"}`); + Object* o = json::parse(mem, &reader)!!; + defer o.free(); +} + + +fn void test_string() +{ + Object* o = json::parse_string(mem, `{"jsonrpc":"2","id":null,"method":"initialize"}`)!!; + defer o.free(); + String s = string::tformat("%s", *o); + Object* o2 = json::parse_string(mem, s)!!; + defer o2.free(); + String s2 = string::tformat("%s", *o2); + assert(s2 == s, "Unexpectedly got %s and not %s", s2, s); +} + +fn void test_temp_string() +{ + @pool() + { + Object* o = json::tparse_string(`{"jsonrpc":"2","id":null,"method":"initialize"}`)!!; + defer o.free(); + String s = string::tformat("%s", *o); + Object* o2 = json::tparse_string(s)!!; + defer o2.free(); + String s2 = string::tformat("%s", *o2); + assert(s2 == s, "Unexpectedly got %s and not %s", s2, s); + }; +} diff --git a/test/unit7/stdlib/hash/fnv32a.c3 b/test/unit7/stdlib/hash/fnv32a.c3 new file mode 100644 index 000000000..553a93a0e --- /dev/null +++ b/test/unit7/stdlib/hash/fnv32a.c3 @@ -0,0 +1,24 @@ +module std::hash::fnv32a_test @test; +import std::hash::fnv32a; + +fn void test_fnv32a() +{ + Fnv32a hash; + + char[] input = "Hello world"; + uint want = 0x594d29c7; + + // update + hash.init(); + hash.update(input); + assert ((uint)hash == want, "got: %d, want: %d", hash, want); + + // update_char + hash.init(); + foreach (c : input) hash.update_char(c); + assert ((uint)hash == want, "got: %d, want: %d", hash, want); + + // encode + uint encoded = fnv32a::encode(input); + assert (encoded == want, "got: %d, want: %d", encoded, want); +} diff --git a/test/unit7/stdlib/hash/fnv64a.c3 b/test/unit7/stdlib/hash/fnv64a.c3 new file mode 100644 index 000000000..60dcd910c --- /dev/null +++ b/test/unit7/stdlib/hash/fnv64a.c3 @@ -0,0 +1,24 @@ +module std::hash::fnv64a_test @test; +import std::hash::fnv64a; + +fn void test_fnv64a() +{ + Fnv64a hash; + + char[] input = "Hello world"; + ulong want = 0x2713f785a33764c7; + + // update + hash.init(); + hash.update(input); + assert ((ulong)hash == want, "got: %d, want: %d", hash, want); + + // update_char + hash.init(); + foreach (c : input) hash.update_char(c); + assert ((ulong)hash == want, "got: %d, want: %d", hash, want); + + // encode + ulong encoded = fnv64a::encode(input); + assert (encoded == want, "got: %d, want: %d", encoded, want); +} diff --git a/test/unit7/stdlib/hash/md5.c3 b/test/unit7/stdlib/hash/md5.c3 new file mode 100644 index 000000000..eb28d5de9 --- /dev/null +++ b/test/unit7/stdlib/hash/md5.c3 @@ -0,0 +1,41 @@ +module std::hash::md5_test @test; +import std::hash::md5; + + +fn void test_md5_rfc() +{ + Md5 md5; + md5.init(); + assert (md5.final() == x'd41d8cd98f00b204e9800998ecf8427e'); + + md5.init(); + md5.update("a"); + assert (md5.final() == x'0cc175b9c0f1b6a831c399e269772661'); + + md5.init(); + md5.update("abc"); + assert (md5.final() == x'900150983cd24fb0d6963f7d28e17f72'); + + md5.init(); + md5.update("message "); + md5.update("digest"); + assert(md5.final() == x'f96b697d7cb7938d525a2f31aaf161d0'); + + md5.init(); + md5.update("abcdefghijklmnopqrstuvwxyz"); + assert(md5.final() == x'c3fcd3d76192e4007dfb496cca67e13b'); + + md5.init(); + md5.update("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(md5.final() == x'd174ab98d277d9f5a5611c2c9f419d9f'); + + md5.init(); + md5.update("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + assert(md5.final() == x'57edf4a22be3c955ac49da2e2107b67a'); + +} + +fn void test_md5_hash() +{ + assert(md5::hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890") == x'57edf4a22be3c955ac49da2e2107b67a'); +} diff --git a/test/unit7/stdlib/hash/sha1.c3 b/test/unit7/stdlib/hash/sha1.c3 new file mode 100644 index 000000000..472a74fbb --- /dev/null +++ b/test/unit7/stdlib/hash/sha1.c3 @@ -0,0 +1,70 @@ +module std::hash::sha1_test @test; +import std::hash::sha1; + +fn void test_sha1_abc() +{ + Sha1 sha; + sha.init(); + sha.update("abc"); + assert(sha.final() == x"A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D"); +} + +fn void test_sha1_even_longer() +{ + char[] content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce viverra nulla nec neque convallis feugiat. Vestibulum sodales at."; + Sha1 sha1; + sha1.init(); + sha1.update(content); + assert(sha1.final() == x"7ba51bc42c21e6159556537c2134d1c2028799ef"); +} + +fn void test_sha1_longer() +{ + Sha1 sha; + sha.init(); + sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(sha.final() == x"84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1"); +} + +fn void test_pbkdf2() +{ + char[] pw = "password"; + char[] s = "salt"; + char[20] out; + sha1::pbkdf2(pw, s, 1, &out); + assert(out == x'0c60c80f961f0e71f3a9b524af6012062fe037a6'); + sha1::pbkdf2(pw, s, 2, &out); + assert(out == x'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957'); + sha1::pbkdf2(pw, s, 4096, &out); + assert(out == x'4b007901b765489abead49d926f721d065a429c1'); +} + +fn void test_pbkdf2_2() +{ + char[] pw = "passwordPASSWORDpassword"; + char[] s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; + char[25] out; + sha1::pbkdf2(pw, s, 4096, &out); + assert(out == x'3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038'); +} + +fn void test_pbkdf2_3() +{ + char[] pw = "pass\0word"; + char[] salt = "sa\0lt"; + char[16] out; + sha1::pbkdf2(pw, salt, 4096, &out); + assert(out == x'56fa6aa75548099dcc37d7f03425e0c3'); +} + +fn void test_sha1_million_a() +{ + Sha1 sha; + sha.init(); + const int COUNT = 1_000_000; + for (int i = 0; i < COUNT / 10; i++) + { + sha.update("aaaaaaaaaa"); + } + assert(sha.final() == x"34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"); +} \ No newline at end of file diff --git a/test/unit7/stdlib/hash/sha256.c3 b/test/unit7/stdlib/hash/sha256.c3 new file mode 100644 index 000000000..d14c15801 --- /dev/null +++ b/test/unit7/stdlib/hash/sha256.c3 @@ -0,0 +1,74 @@ +module std::hash::sha256_test @test; +import std::hash::sha256; + +fn void test_sha256_empty() +{ + Sha256 sha; + sha.init(); + sha.update(""); + + assert(sha.final() == x"E3B0C442 98FC1C14 9AFBF4C8 996FB924 27AE41E4 649B934C A495991B 7852B855"); +} + +fn void test_sha256_abc() +{ + Sha256 sha; + sha.init(); + sha.update("abc"); + + assert(sha.final() == x"BA7816BF 8F01CFEA 414140DE 5DAE2223 B00361A3 96177A9C B410FF61 F20015AD"); +} + +fn void test_sha256_longer() +{ + Sha256 sha; + sha.init(); + sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqabcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(sha.final() == x"59F109D9 533B2B70 E7C3B814 A2BD218F 78EA5D37 14455BC6 7987CF0D 664399CF"); +} + +fn void test_pbkdf2() +{ + char[] pw = "password"; + char[] s = "salt"; + char[32] out; + sha256::pbkdf2(pw, s, 1, &out); + assert(out == x'120FB6CF FCF8B32C 43E72252 56C4F837 A86548C9 2CCC3548 0805987C B70BE17B'); + sha256::pbkdf2(pw, s, 2, &out); + assert(out == x'AE4D0C95 AF6B46D3 2D0ADFF9 28F06DD0 2A303F8E F3C251DF D6E2D85A 95474C43'); + sha256::pbkdf2(pw, s, 4096, &out); + assert(out == x'C5E478D5 9288C841 AA530DB6 845C4C8D 962893A0 01CE4E11 A4963873 AA98134A'); +} + +fn void test_pbkdf2_2() +{ + char[] pw = "passwordPASSWORDpassword"; + char[] s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; + char[32] out; + sha256::pbkdf2(pw, s, 4096, &out); + assert(out == x'348C89DB CBD32B2F 32D814B8 116E84CF 2B17347E BC180018 1C4E2A1F B8DD53E1'); +} + + +fn void test_pbkdf2_3() +{ + char[] pw = "pass\0word"; + char[] salt = "sa\0lt"; + char[32] out; + sha256::pbkdf2(pw, salt, 4096, &out); + + assert(out == x'89B69D05 16F82989 3C696226 650A8687 8C029AC1 3EE27650 9D5AE58B 6466A724'); +} + +fn void test_sha256_million_a() +{ + Sha256 sha; + sha.init(); + const int COUNT = 1_000_000; + for (int i = 0; i < COUNT / 10; i++) + { + sha.update("aaaaaaaaaa"); + } + + assert(sha.final() == x"CDC76E5C 9914FB92 81A1C7E2 84D73E67 F1809A48 A497200E 046D39CC C7112CD0"); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/bits.c3 b/test/unit7/stdlib/io/bits.c3 new file mode 100644 index 000000000..bd2afd9bc --- /dev/null +++ b/test/unit7/stdlib/io/bits.c3 @@ -0,0 +1,249 @@ +module std::io::bits @test; +import std::io; + +fn void test_write_0b1() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b11111111, 1)!!; + bw.flush()!!; + + assert(w.str_view() == x"80"); // 0b1000 0000 +} + +fn void test_write_0b1111() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b11111111, 4)!!; + bw.flush()!!; + + assert(w.str_view() == x"f0"); // 0b1111 0000 +} + +fn void test_write_0b1111_1111() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b11111111, 8)!!; + bw.flush()!!; + + assert(w.str_view() == x"ff"); +} + +fn void test_write_0b1000() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b0001000, 4)!!; + bw.flush()!!; + + assert(w.str_view() == x"80"); // 0b1000 0000 +} + +fn void test_write_0b01000() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b0001000, 5)!!; + bw.flush()!!; + + assert(w.str_view() == x"40"); // 0b0100 0000 +} + +fn void test_write_0b0001() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b0000001, 4)!!; + bw.flush()!!; + + assert(w.str_view() == x"10"); // 0b0001 0000 +} + + +fn void test_write_0b0000_0001() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b0000001, 8)!!; + bw.flush()!!; + + assert(w.str_view() == x"01"); // 0b0000 0001 +} + +fn void test_write_10_bits() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0x789, 10)!!; // 01|11 1000 1001 + bw.flush()!!; + + // e 2 4 0 + assert(w.str_view() == x"e2 40"); // 0b1110 0010 0100 0000 +} + +fn void test_write_16_bits() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xfafb, 16)!!; + bw.flush()!!; + + assert(w.str_view() == x"fa fb"); +} + +fn void test_write_24_bits() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xfafbfc, 24)!!; + bw.flush()!!; + + assert(w.str_view() == x"fa fb fc"); +} + +fn void test_write_30_bits() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xf0f1f2f3, 30)!!; // 11 | 110000111100011111001011110011 + bw.flush()!!; + // c 3 c 7 c b c c + assert(w.str_view() == x"c3 c7 cb cc"); // 1100 0011 1100 0111 1100 1011 1100 1100 +} + +fn void test_write_32_bits() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xfafbfcfd, 32)!!; + bw.flush()!!; + + assert(w.str_view() == x"fa fb fc fd"); +} + +fn void test_write_2_bits_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0b11111111, 2)!!; + bw.write_bits(0b00000001, 2)!!; + bw.write_bits(0b11111111, 2)!!; + bw.flush()!!; + + assert(w.str_view() == x"dc"); // 0b1101 1100 +} + +fn void test_write_10_bits_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0x789, 10)!!; // 01 | 11 1000 1001 + bw.write_bits(0xabc, 10)!!; // 10 | 10 1011 1100 + bw.write_bits(0xdef, 10)!!; // 11 | 01 1110 1111 + bw.flush()!!; + + // e 2 6 b c 7 b c + assert(w.str_view() == x"e2 6b c7 bc"); // 0b1110 0010 0110 1011 1100 0111 1011 1100 +} + +fn void test_write_24_bits_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xfafbfc, 24)!!; + bw.write_bits(0xfdfeff, 24)!!; + bw.flush()!!; + + assert(w.str_view() == x"fa fb fc fd fe ff"); +} + +fn void test_write_30_bits_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xf0f1f2f3, 30)!!; // 11 | 110000111100011111001011110011 + bw.write_bits(0xfafbfcfd, 30)!!; // 11 | 111010111110111111110011111101 + bw.flush()!!; + + assert(w.str_view() == x"c3 c7 cb cf af bf cf d0"); +} + +fn void test_write_32_bits_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xf0f1f2f3, 32)!!; + bw.write_bits(0xfafbfcfd, 32)!!; + bw.flush()!!; + + assert(w.str_view() == x"f0 f1 f2 f3 fa fb fc fd"); +} + +fn void test_write_mixed_multiple() { + ByteWriter w; + w.temp_init(); + + BitWriter bw; + bw.init(&w); + + bw.write_bits(0xf0f1f2f3, 8)!!; + bw.write_bits(0xf0f1f2f3, 32)!!; + bw.write_bits(0xfafbfcfd, 30)!!; + bw.write_bits(0xf4f5f6f7, 10)!!; + bw.flush()!!; + + assert(w.str_view() == x"f3 f0 f1 f2 f3 eb ef f3 f6 f7"); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/bufferstream.c3 b/test/unit7/stdlib/io/bufferstream.c3 new file mode 100644 index 000000000..76a3b0d89 --- /dev/null +++ b/test/unit7/stdlib/io/bufferstream.c3 @@ -0,0 +1,94 @@ +module std::io @test; + +const DATA = "Lorem ipsum blandit."; + +fn void readbuffer_large() +{ + ByteReader src; + src.init(DATA); + char[DATA.len] buf; + ReadBuffer reader_buf; + reader_buf.init(&src, buf[..]); + + char[DATA.len] bytes; + usz n = reader_buf.read(bytes[..])!!; + + assert(n == DATA.len, "large read failed: got %d; want %d", n, DATA.len); + String got = (String)bytes[..]; + assert(got == DATA, "large read failed: got %s; want %s", got, DATA); +} + +fn void readbuffer() +{ + ByteReader src; + src.init(DATA); + char[3] buf; + ReadBuffer reader_buf; + reader_buf.init(&src, buf[..]); + + ByteWriter bw; + bw.temp_init(); + + usz n = io::copy_to(&reader_buf, &bw)!!; + + assert(n == DATA.len, "got %d; want %d", n, DATA.len); + String got = bw.str_view(); + assert(got == DATA, "got %s; want %s", got, DATA); +} + +fn void writebuffer_large() +{ + ByteWriter out; + out.temp_init(); + char[16] buf; + WriteBuffer write_buf; + write_buf.init(&out, buf[..]); + + usz n = write_buf.write(DATA)!!; + + assert(n == DATA.len, "large write failed: got %d; want %d", n, DATA.len); + String got = out.str_view(); + assert(got == DATA, "large write failed: got %s; want %s", got, DATA); +} + +fn void writebuffer() +{ + ByteReader br; + br.init(DATA); + ByteWriter out; + out.temp_init(); + char[3] buf; + WriteBuffer write_buf; + write_buf.init(&out, buf[..]); + + usz n = io::copy_to(&br, &write_buf)!!; + + assert(n == DATA.len, "got %d; want %d", n, DATA.len); + String got = out.str_view(); + assert(got == DATA, "got %s; want %s", got, DATA); +} + +fn void writebuffer_write_byte() +{ + ByteWriter out; + out.temp_init(); + char[2] buf; + WriteBuffer write_buf; + write_buf.init(&out, buf[..]); + + write_buf.write_byte('a')!!; + assert(write_buf.str_view() == "a"); + assert(out.str_view() == ""); + + write_buf.write_byte('b')!!; + assert(write_buf.str_view() == "ab"); + assert(out.str_view() == ""); + + write_buf.write_byte('c')!!; + assert(write_buf.str_view() == "c"); + assert(out.str_view() == "ab"); + + write_buf.flush()!!; + assert(write_buf.str_view() == ""); + assert(out.str_view() == "abc"); +} diff --git a/test/unit7/stdlib/io/bytebuffer.c3 b/test/unit7/stdlib/io/bytebuffer.c3 new file mode 100644 index 000000000..eafbe9ec9 --- /dev/null +++ b/test/unit7/stdlib/io/bytebuffer.c3 @@ -0,0 +1,29 @@ +module std::io::bytebuffer @test; +import std::io; + +fn void write_read() +{ + ByteBuffer buffer; + buffer.new_init(0); + defer buffer.free(); + buffer.write("hello")!!; + + char[8] bytes; + usz n = buffer.read(bytes[..])!!; + assert(n == 5); + assert((String)bytes[:n] == "hello"); + + buffer.write("hello world")!!; + n = buffer.read(bytes[..])!!; + assert(n == bytes.len); + assert((String)bytes[:n] == "hello wo"); + assert(buffer.read_idx == 1); + + char c = buffer.read_byte()!!; + assert(c == 'r'); + buffer.pushback_byte()!!; + + n = buffer.read(bytes[..])!!; + assert((String)bytes[:n] == "rld"); + assert(buffer.read_idx == 1); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/bytestream.c3 b/test/unit7/stdlib/io/bytestream.c3 new file mode 100644 index 000000000..3c9384090 --- /dev/null +++ b/test/unit7/stdlib/io/bytestream.c3 @@ -0,0 +1,72 @@ +module std::io @test; + +fn void bytestream() +{ + ByteReader r; + r.init("abc"); + InStream s = &r; + assert(s.len() == 3); + char[5] buffer; + assert('a' == s.read_byte()!!); + s.pushback_byte()!!; + usz len = s.read(&buffer)!!; + assert((String)buffer[:len] == "abc"); + ByteWriter w; + w.new_init(); + defer (void)w.destroy(); + OutStream ws = &w; + ws.write("helloworld")!!; + assert(w.str_view() == "helloworld"); + s.seek(0, SET)!!; + io::copy_to(s, ws)!!; + s.seek(1, SET)!!; + s.write_to(ws)!!; + assert(w.str_view() == "helloworldabcbc"); +} + +fn void bytewriter_buffer() +{ + ByteWriter writer; + char[8] z; + writer.init_with_buffer(&z); + OutStream s = &writer; + s.write("hello")!!; + s.write_byte(0)!!; + String o = ((ZString)&z).str_view(); + assert(o == "hello"); + assert(@catch(s.write("xxxx"))); +} + +fn void bytewriter_read_from() +{ + char[] data = "Lorem ipsum dolor sit amet biam."; + TestReader r = { .bytes = data }; + InStream s = &r; + + ByteWriter bw; + bw.temp_init(); + bw.read_from(s)!!; + + assert(bw.str_view() == data); +} + +module std::io; +// TestReader only has the read method to trigger the path +// in ByteWriter.read_from that does not rely on the available method. +struct TestReader (InStream) +{ + char[] bytes; + usz index; +} + +fn usz! TestReader.read(&self, char[] bytes) @dynamic +{ + usz left = self.bytes.len - self.index; + if (left == 0) return 0; + usz n = min(left, bytes.len); + mem::copy(bytes.ptr, &self.bytes[self.index], n); + self.index += n; + return n; +} + +fn char! TestReader.read_byte(&self) @dynamic => io::read_byte_using_read(self); diff --git a/test/unit7/stdlib/io/dstringstream.c3 b/test/unit7/stdlib/io/dstringstream.c3 new file mode 100644 index 000000000..aa7bfaf33 --- /dev/null +++ b/test/unit7/stdlib/io/dstringstream.c3 @@ -0,0 +1,18 @@ +module std::io @test; + +fn void test_writing() +{ + DString foo; + foo.init(mem); + defer foo.free(); + OutStream s = &foo; + s.write("hello")!!; + s.write_byte('-')!!; + s.write("what?-------------------------------------------------------")!!; + + ByteReader r; + String test_str = "2134"; + io::copy_to(r.init(test_str), s)!!; + String o = foo.str_view(); + assert(o == "hello-what?-------------------------------------------------------2134"); +} diff --git a/test/unit7/stdlib/io/file_read_write_any.c3 b/test/unit7/stdlib/io/file_read_write_any.c3 new file mode 100644 index 000000000..23e9eab45 --- /dev/null +++ b/test/unit7/stdlib/io/file_read_write_any.c3 @@ -0,0 +1,94 @@ +module std::io::file @test; + +import std::io; + +struct Data +{ + bool boolean; + char byte; + ichar s_byte; + ushort word; + short s_word; + uint dword; + int s_dword; + ulong qword; + long s_qword; + uint128 dqword; + int128 s_dqword; + char[128] data; + bool[57] flags; + bitstruct : ushort + { + char upper : 8..15; + char lower : 0..7; + } + bitstruct : ushort @overlap + { + ushort total : 0..15; + char t_lower : 0..7; + char t_upper : 8..15; + bool t_rand : 12; + bool t_other : 4; + } + union + { + char hello; + short hallo; + uint byebye; + } +} + +fn void read_write_any() +{ + Data data; + data.boolean = false; + data.byte = 0xAF; + data.s_byte = -56; + data.word = 0xBEAF; + data.s_word = -547; + data.qword = 0xCAFEAFBC; + data.s_qword = -2000000000; + data.dqword = 0xCAFEADDEAFBEFF; + data.s_dqword = -45600000000000000; + for (int i = 0; i < 128; ++i) data.data[i] = (char)(255 - i); + for (int i = 0; i < 57; ++i) data.flags[i] = (i % 4) == 0; + data.upper = 0x56; + data.lower = 0x44; + data.total = 0xA55A; + data.hello = 0xCC; + + File file = file::open("tmp/__file_read_write_any_test_file", "wb")!!; + + io::write_any(&file, &data)!!; + file.flush()!!; + file.close()!!; + + file = file::open("tmp/__file_read_write_any_test_file", "rb")!!; + Data rdata; + io::read_any(&file, &rdata)!!; + + file.close()!!; + + assert(rdata.boolean == data.boolean); + assert(rdata.byte == data.byte); + assert(rdata.s_byte == data.s_byte); + assert(rdata.word == data.word); + assert(rdata.s_word == data.s_word); + assert(rdata.dword == data.dword); + assert(rdata.s_dword == data.s_dword); + assert(rdata.qword == data.qword); + assert(rdata.s_qword == data.s_qword); + assert(rdata.dqword == data.dqword); + assert(rdata.data == data.data); + assert(rdata.flags == data.flags); + assert(rdata.upper == data.upper); + assert(rdata.lower == data.lower); + assert(rdata.t_lower == data.t_lower); + assert(rdata.t_upper == data.t_upper); + assert(rdata.t_rand == data.t_rand); + assert(rdata.t_other == data.t_other); + assert(rdata.total == data.total); + assert(rdata.hello == data.hello); + assert(rdata.hallo == data.hallo); + assert(rdata.byebye == data.byebye); +} diff --git a/test/unit7/stdlib/io/fileinfo.c3 b/test/unit7/stdlib/io/fileinfo.c3 new file mode 100644 index 000000000..b682f6ab6 --- /dev/null +++ b/test/unit7/stdlib/io/fileinfo.c3 @@ -0,0 +1,16 @@ +module std::io::fileinfo @test; +import std::io::os; + +fn void test_native_is_file() @if(env::LINUX || env::DARWIN) +{ + assert(!os::native_is_file("/dev/loop0")); + assert(!os::native_is_file("/dev/null")); +} + +fn void test_native_is_dir() @if(env::LINUX || env::DARWIN) +{ + assert(os::native_is_dir("/")); + assert(!os::native_is_file("/")); + assert(!os::native_is_dir("/dev/loop0")); + assert(!os::native_is_dir("/dev/null")); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/limitreader.c3 b/test/unit7/stdlib/io/limitreader.c3 new file mode 100644 index 000000000..269a40025 --- /dev/null +++ b/test/unit7/stdlib/io/limitreader.c3 @@ -0,0 +1,20 @@ +module std::io @test; + + +fn void limitreader() +{ + const DATA = "Hello World!"; + ByteReader src; + src.init(DATA); + const LIMIT = 5; + LimitReader lmr; + lmr.init(&src, LIMIT); + + char[DATA.len] bytes; + usz n = lmr.read(bytes[..])!!; + + assert(n == LIMIT, "got %d; want %d", n, LIMIT); + String got = (String)bytes[:n]; + String want = DATA[:LIMIT]; + assert(got == want, "got %d; want %d", got, want); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/multireader.c3 b/test/unit7/stdlib/io/multireader.c3 new file mode 100644 index 000000000..747f9b219 --- /dev/null +++ b/test/unit7/stdlib/io/multireader.c3 @@ -0,0 +1,21 @@ +module std::io @test; + +fn void test_multireader() +{ + MultiReader mr; + mr.temp_init( + &&wrap_bytes("foo"), + &&wrap_bytes(" "), + &&wrap_bytes("bar"), + &&wrap_bytes("!"), + ); + defer mr.free(); + + ByteWriter w; + io::copy_to(&mr, w.temp_init())!!; + + String want = "foo bar!"; + assert(w.str_view() == want, + "invalid data read; got: %s, want: %s", w.str_view(), want); +} + diff --git a/test/unit7/stdlib/io/multiwriter.c3 b/test/unit7/stdlib/io/multiwriter.c3 new file mode 100644 index 000000000..8261bab34 --- /dev/null +++ b/test/unit7/stdlib/io/multiwriter.c3 @@ -0,0 +1,17 @@ +module std::io @test; + +fn void test_multiwriter() +{ + ByteWriter w1, w2; + MultiWriter mw; + mw.temp_init(w1.temp_init(), w2.temp_init()); + defer mw.free(); + + String want = "foobar"; + io::copy_to((ByteReader){}.init(want), &mw)!!; + + assert(w1.str_view() == want, + "invalid write; got: %s, want: %s", w1.str_view(), want); + assert(w2.str_view() == want, + "invalid write; got: %s, want: %s", w2.str_view(), want); +} diff --git a/test/unit7/stdlib/io/path.c3 b/test/unit7/stdlib/io/path.c3 new file mode 100644 index 000000000..ebf8eefd3 --- /dev/null +++ b/test/unit7/stdlib/io/path.c3 @@ -0,0 +1,378 @@ +module std::io::path @test; + +fn void test_dot() +{ + Path p = path::new(mem, ".")!!; + defer p.free(); + assert(@catch(p.parent())); + // It must be possible to form the absolute version. + Path p2 = p.absolute(mem)!!; + p2.free(); + p2 = p.append(mem, "/hello/world")!!; + if (p2.env == POSIX) + { + assert(p2.str_view() == "hello/world"); + } + else + { + assert(p2.str_view() == `hello\world`); + } + p2.free(); +} + +fn void test_parent() +{ + Path p = path::new(mem, "")!!; + assert(@catch(p.parent())); + p.free(); + p = path::new(mem, "/", path_env: PathEnv.POSIX)!!; + assert(@catch(p.parent())); + p.free(); + p = path::new(mem, "/a/b/c", path_env: PathEnv.POSIX)!!; + assert(p.parent().str_view()!! == "/a/b"); + p.free(); + p = path::new(mem, "/a/b/c", path_env: PathEnv.WIN32)!!; + assert(p.parent().str_view()!! == `\a\b`); + p.free(); +} + +fn void test_path_normalized() => mem::@scoped(allocator::temp()) +{ + assert(path::new(mem, "", path_env: PathEnv.WIN32).str_view()!! == ""); + assert(@catch(path::new(mem, "1:\\a\\b\\c.txt", path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, ":", path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, "1:", path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, "1:a", path_env: PathEnv.WIN32))); +// assert(@catch(path::new(mem, `\\\a\b\c.txt`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `\\server\a\b\..\..\..\c`, path_env: PathEnv.WIN32))); + + assert(@catch(path::new(mem, `\\a`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `/a/b/../../../c`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `/a/b/../../../c`, path_env: PathEnv.POSIX))); + assert(@catch(path::new(mem, `/a/b/../../..`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `/a/b/../../..`, path_env: PathEnv.POSIX))); + assert(@catch(path::new(mem, `/../a`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `/../a`, path_env: PathEnv.POSIX))); + assert(@catch(path::new(mem, `/..`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `/..`, path_env: PathEnv.POSIX))); + assert(@catch(path::new(mem, `C:/a/b/../../../c`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `C:/../a`, path_env: PathEnv.WIN32))); + assert(@catch(path::new(mem, `C:/..`, path_env: PathEnv.WIN32))); + + assert(path::new(mem, "/", path_env: PathEnv.POSIX).str_view()!! == "/"); + assert(path::new(mem, "/./", path_env: PathEnv.POSIX).str_view()!! == "/"); + assert(path::new(mem, "/foo/../", path_env: PathEnv.POSIX).str_view()!! == "/"); + assert(path::new(mem, "/foo/bar/../", path_env: PathEnv.POSIX).str_view()!! == "/foo"); + assert(path::new(mem, "/foo//bar", path_env: PathEnv.POSIX).str_view()!! == "/foo/bar"); + assert(path::new(mem, "/foo//bar/../", path_env: PathEnv.POSIX).str_view()!! == "/foo"); + assert(path::new(mem, "/foo/.bar", path_env: PathEnv.POSIX).str_view()!! == "/foo/.bar"); + assert(path::new(mem, `\foo\.bar`, path_env: PathEnv.WIN32).str_view()!! == `\foo\.bar`); + assert(path::new(mem, "a\\b/c.txt", path_env: PathEnv.WIN32).str_view()!! == `a\b\c.txt`); + assert(path::new(mem, "a\\b/c.txt", path_env: PathEnv.POSIX).str_view()!! == "a\\b/c.txt"); + assert(path::new(mem, "C:\\a\\b/c.txt", path_env: PathEnv.WIN32).str_view()!! == `C:\a\b\c.txt`); + assert(path::new(mem, "C:\\a\\b/c.txt", path_env: PathEnv.POSIX).str_view()!! == "C:\\a\\b/c.txt"); + assert(path::new(mem, `\\server\a\b/c.txt`, path_env: PathEnv.WIN32).str_view()!! == `\\server\a\b\c.txt`); + assert(path::new(mem, `\\server\a\b/c.txt`, path_env: PathEnv.POSIX).str_view()!! == `\\server\a\b/c.txt`); + assert(path::new(mem, `c:\hello//bar\\\\foo.txt`, path_env: PathEnv.WIN32).str_view()!! == `c:\hello\bar\foo.txt`); + + assert(path::new(mem, `~\a\b/c.txt`, path_env: PathEnv.WIN32).str_view()!! == `~\a\b\c.txt`); + assert(path::new(mem, `~\a\b/c.txt`, path_env: PathEnv.POSIX).str_view()!! == `~\a\b/c.txt`); + + + assert(path::new(mem, `a/b/../../../c`, path_env: PathEnv.WIN32).str_view()!! == `..\c`); + assert(path::new(mem, `a/b/../../../c`, path_env: PathEnv.POSIX).str_view()!! == `../c`); + assert(path::new(mem, `a/b/../../..`, path_env: PathEnv.WIN32).str_view()!! == `..`); + assert(path::new(mem, `a/b/../../..`, path_env: PathEnv.POSIX).str_view()!! == `..`); + assert(path::new(mem, `../a`, path_env: PathEnv.WIN32).str_view()!! == `..\a`); + assert(path::new(mem, `../a`, path_env: PathEnv.POSIX).str_view()!! == `../a`); + assert(path::new(mem, `..`, path_env: PathEnv.WIN32).str_view()!! == `..`); + assert(path::new(mem, `..`, path_env: PathEnv.POSIX).str_view()!! == `..`); + assert(path::new(mem, `a/b/../c`, path_env: PathEnv.WIN32).str_view()!! == `a\c`); + assert(path::new(mem, `a/b/../c`, path_env: PathEnv.POSIX).str_view()!! == `a/c`); + assert(path::new(mem, `a/b/../../c`, path_env: PathEnv.WIN32).str_view()!! == `c`); + assert(path::new(mem, `a/b/../../c`, path_env: PathEnv.POSIX).str_view()!! == `c`); + assert(path::new(mem, `a/b/..`, path_env: PathEnv.WIN32).str_view()!! == `a`); + assert(path::new(mem, `a/b/..`, path_env: PathEnv.POSIX).str_view()!! == `a`); + assert(path::new(mem, `a/b/../`, path_env: PathEnv.WIN32).str_view()!! == `a`); + assert(path::new(mem, `a/b/../`, path_env: PathEnv.POSIX).str_view()!! == `a`); + assert(path::new(mem, `a/b/../..`, path_env: PathEnv.WIN32).str_view()!! == "."); + assert(path::new(mem, `a/b/../..`, path_env: PathEnv.POSIX).str_view()!! == "."); + assert(path::new(mem, `a/b/../../`, path_env: PathEnv.WIN32).str_view()!! == "."); + assert(path::new(mem, `a/b/../../`, path_env: PathEnv.POSIX).str_view()!! == "."); + assert(path::new(mem, `a/b/../c/../d`, path_env: PathEnv.WIN32).str_view()!! == `a\d`); + assert(path::new(mem, `a/b/../c/../d`, path_env: PathEnv.POSIX).str_view()!! == `a/d`); + assert(path::new(mem, `a/b/../c/../d/`, path_env: PathEnv.WIN32).str_view()!! == `a\d`); + assert(path::new(mem, `a/b/../c/../d/`, path_env: PathEnv.POSIX).str_view()!! == `a/d`); + assert(path::new(mem, `a/b//d`, path_env: PathEnv.WIN32).str_view()!! == `a\b\d`); + assert(path::new(mem, `a/b//d`, path_env: PathEnv.POSIX).str_view()!! == `a/b/d`); + assert(path::new(mem, `a/b/././.`, path_env: PathEnv.WIN32).str_view()!! == `a\b`); + assert(path::new(mem, `a/b/././.`, path_env: PathEnv.POSIX).str_view()!! == `a/b`); + assert(path::new(mem, `a/b/./././`, path_env: PathEnv.WIN32).str_view()!! == `a\b`); + assert(path::new(mem, `a/b/./././`, path_env: PathEnv.POSIX).str_view()!! == `a/b`); + assert(path::new(mem, `./a/`, path_env: PathEnv.WIN32).str_view()!! == `a`); + assert(path::new(mem, `./a/`, path_env: PathEnv.POSIX).str_view()!! == `a`); + assert(path::new(mem, `./`, path_env: PathEnv.WIN32).str_view()!! == `.`); + assert(path::new(mem, `./`, path_env: PathEnv.POSIX).str_view()!! == `.`); + assert(path::new(mem, `.`, path_env: PathEnv.WIN32).str_view()!! == `.`); + assert(path::new(mem, `.`, path_env: PathEnv.POSIX).str_view()!! == `.`); + assert(path::new(mem, ``, path_env: PathEnv.WIN32).str_view()!! == ``); + assert(path::new(mem, ``, path_env: PathEnv.POSIX).str_view()!! == ``); + assert(path::new(mem, `/a`, path_env: PathEnv.WIN32).str_view()!! == `\a`); + assert(path::new(mem, `/a`, path_env: PathEnv.POSIX).str_view()!! == `/a`); + assert(path::new(mem, `/a/`, path_env: PathEnv.WIN32).str_view()!! == `\a`); + assert(path::new(mem, `/a/`, path_env: PathEnv.POSIX).str_view()!! == `/a`); + assert(path::new(mem, `/a/b/../c`, path_env: PathEnv.WIN32).str_view()!! == `\a\c`); + assert(path::new(mem, `/a/b/../c`, path_env: PathEnv.POSIX).str_view()!! == `/a/c`); + assert(path::new(mem, `/a/b/../../c`, path_env: PathEnv.WIN32).str_view()!! == `\c`); + assert(path::new(mem, `/a/b/../../c`, path_env: PathEnv.POSIX).str_view()!! == `/c`); + assert(path::new(mem, `/a/b/..`, path_env: PathEnv.WIN32).str_view()!! == `\a`); + assert(path::new(mem, `/a/b/..`, path_env: PathEnv.POSIX).str_view()!! == `/a`); + assert(path::new(mem, `/a/b/../..`, path_env: PathEnv.WIN32).str_view()!! == `\`); + assert(path::new(mem, `/a/b/../..`, path_env: PathEnv.POSIX).str_view()!! == `/`); + assert(path::new(mem, `/a/b/../c/../d`, path_env: PathEnv.WIN32).str_view()!! == `\a\d`); + assert(path::new(mem, `/a/b/../c/../d`, path_env: PathEnv.POSIX).str_view()!! == `/a/d`); + assert(path::new(mem, `/a/b//d`, path_env: PathEnv.WIN32).str_view()!! == `\a\b\d`); + assert(path::new(mem, `/a/b//d`, path_env: PathEnv.POSIX).str_view()!! == `/a/b/d`); + assert(path::new(mem, `/./a/`, path_env: PathEnv.WIN32).str_view()!! == `\a`); + assert(path::new(mem, `/./a/`, path_env: PathEnv.POSIX).str_view()!! == `/a`); + assert(path::new(mem, `/./`, path_env: PathEnv.WIN32).str_view()!! == `\`); + assert(path::new(mem, `/./`, path_env: PathEnv.POSIX).str_view()!! == `/`); + assert(path::new(mem, `/.`, path_env: PathEnv.WIN32).str_view()!! == `\`); + assert(path::new(mem, `/.`, path_env: PathEnv.POSIX).str_view()!! == `/`); + assert(path::new(mem, `/`, path_env: PathEnv.WIN32).str_view()!! == `\`); + assert(path::new(mem, `/`, path_env: PathEnv.POSIX).str_view()!! == `/`); + assert(path::new(mem, `C:/a`, path_env: PathEnv.WIN32).str_view()!! == `C:\a`); + assert(path::new(mem, `C:/a`, path_env: PathEnv.POSIX).str_view()!! == `C:/a`); + assert(path::new(mem, `C:/a/b/../c`, path_env: PathEnv.WIN32).str_view()!! == `C:\a\c`); + assert(path::new(mem, `C:/a/b/../c`, path_env: PathEnv.POSIX).str_view()!! == `C:/a/c`); + assert(path::new(mem, `C:/a/b/../../c`, path_env: PathEnv.WIN32).str_view()!! == `C:\c`); + assert(path::new(mem, `C:/a/b/../../c`, path_env: PathEnv.POSIX).str_view()!! == `C:/c`); + assert(path::new(mem, `C:/a/b/../../../c`, path_env: PathEnv.POSIX).str_view()!! == `c`); + assert(path::new(mem, `C:/a/b/..`, path_env: PathEnv.WIN32).str_view()!! == `C:\a`); + assert(path::new(mem, `C:/a/b/..`, path_env: PathEnv.POSIX).str_view()!! == `C:/a`); + assert(path::new(mem, `C:/a/b/../..`, path_env: PathEnv.WIN32).str_view()!! == `C:\`); + assert(path::new(mem, `C:/a/b/../..`, path_env: PathEnv.POSIX).str_view()!! == `C:`); + assert(path::new(mem, `C:/a/b/../c/../d`, path_env: PathEnv.WIN32).str_view()!! == `C:\a\d`); + assert(path::new(mem, `C:/a/b/../c/../d`, path_env: PathEnv.POSIX).str_view()!! == `C:/a/d`); + assert(path::new(mem, `C:/a/b//d`, path_env: PathEnv.WIN32).str_view()!! == `C:\a\b\d`); + assert(path::new(mem, `C:/a/b//d`, path_env: PathEnv.POSIX).str_view()!! == `C:/a/b/d`); + assert(path::new(mem, `C:/a/b/././.`, path_env: PathEnv.WIN32).str_view()!! == `C:\a\b`); + assert(path::new(mem, `C:/a/b/././.`, path_env: PathEnv.POSIX).str_view()!! == `C:/a/b`); + assert(path::new(mem, `C:/./a`, path_env: PathEnv.WIN32).str_view()!! == `C:\a`); + assert(path::new(mem, `C:/./a`, path_env: PathEnv.POSIX).str_view()!! == `C:/a`); + assert(path::new(mem, `C:/./`, path_env: PathEnv.WIN32).str_view()!! == `C:\`); + assert(path::new(mem, `C:/./`, path_env: PathEnv.POSIX).str_view()!! == `C:`); + assert(path::new(mem, `C:/../a`, path_env: PathEnv.POSIX).str_view()!! == `a`); + assert(path::new(mem, `C:/..`, path_env: PathEnv.POSIX).str_view()!! == `.`); + assert(path::new(mem, `C:/`, path_env: PathEnv.WIN32).str_view()!! == `C:\`); + assert(path::new(mem, `C:/`, path_env: PathEnv.POSIX).str_view()!! == `C:`); + assert(path::new(mem, `C:a`, path_env: PathEnv.WIN32).str_view()!! == `C:a`); + assert(path::new(mem, `C:a`, path_env: PathEnv.POSIX).str_view()!! == `C:a`); + assert(path::new(mem, `C:a/`, path_env: PathEnv.WIN32).str_view()!! == `C:a`); + assert(path::new(mem, `C:a/`, path_env: PathEnv.POSIX).str_view()!! == `C:a`); + + assert(path::new(mem, `C:a/b/../c`, path_env: PathEnv.WIN32).str_view()!! == `C:a\c`); + assert(path::new(mem, `C:a/b/../c`, path_env: PathEnv.POSIX).str_view()!! == `C:a/c`); + assert(path::new(mem, `C:a/b/../../c`, path_env: PathEnv.WIN32).str_view()!! == `C:c`); + assert(path::new(mem, `C:a/b/../../c`, path_env: PathEnv.POSIX).str_view()!! == `c`); + assert(path::new(mem, `C:a/b/..`, path_env: PathEnv.WIN32).str_view()!! == `C:a`); + assert(path::new(mem, `C:a/b/..`, path_env: PathEnv.POSIX).str_view()!! == `C:a`); + assert(path::new(mem, `C:a/b/../..`, path_env: PathEnv.WIN32).str_view()!! == `C:`); + assert(path::new(mem, `C:a/b/../..`, path_env: PathEnv.POSIX).str_view()!! == `.`); + assert(path::new(mem, `C:a/b/../c/../d`, path_env: PathEnv.WIN32).str_view()!! == `C:a\d`); + assert(path::new(mem, `C:a/b/../c/../d`, path_env: PathEnv.POSIX).str_view()!! == `C:a/d`); + assert(path::new(mem, `C:a/b//d`, path_env: PathEnv.WIN32).str_view()!! == `C:a\b\d`); + assert(path::new(mem, `C:a/b//d`, path_env: PathEnv.POSIX).str_view()!! == `C:a/b/d`); + assert(path::new(mem, `C:a/b/././.`, path_env: PathEnv.WIN32).str_view()!! == `C:a\b`); + assert(path::new(mem, `C:a/b/././.`, path_env: PathEnv.POSIX).str_view()!! == `C:a/b`); + assert(path::new(mem, `C:a/b/../../../c`, path_env: PathEnv.WIN32).str_view()!! == `C:..\c`); + assert(path::new(mem, `C:./a`, path_env: PathEnv.WIN32).str_view()!! == `C:a`); + assert(path::new(mem, `C:./a`, path_env: PathEnv.POSIX).str_view()!! == `C:./a`); + assert(path::new(mem, `C:./`, path_env: PathEnv.WIN32).str_view()!! == `C:`); + assert(path::new(mem, `C:./`, path_env: PathEnv.POSIX).str_view()!! == `C:.`); + assert(path::new(mem, `C:../a`, path_env: PathEnv.POSIX).str_view()!! == `C:../a`); + assert(path::new(mem, `C:../a`, path_env: PathEnv.WIN32).str_view()!! == `C:..\a`); + assert(path::new(mem, `C:..`, path_env: PathEnv.POSIX).str_view()!! == `C:..`); + assert(path::new(mem, `C:..`, path_env: PathEnv.WIN32).str_view()!! == `C:..`); + assert(path::new(mem, `C:`, path_env: PathEnv.WIN32).str_view()!! == `C:`); + assert(path::new(mem, `C:`, path_env: PathEnv.POSIX).str_view()!! == `C:`); + + assert(path::new(mem, `\\server\foo/a`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a`); + assert(path::new(mem, `\\server\foo/a`, path_env: PathEnv.POSIX).str_view()!! == `\\server\foo/a`); + assert(path::new(mem, `\\server\foo\a\b\..\c`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a\c`); + assert(path::new(mem, `\\server\foo\a\b\..\..\c`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\c`); + assert(path::new(mem, `\\server\foo\a\b\..`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a`); + assert(path::new(mem, `\\server\foo\a\..`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\`); + assert(path::new(mem, `\\server\foo\a\b\..\c\..\d`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a\d`); + assert(path::new(mem, `\\server\foo\a\b\\d`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a\b\d`); + assert(path::new(mem, `\\server\foo\a\b\.\.\.`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a\b`); + assert(path::new(mem, `\\server\foo\.\a`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\a`); + assert(path::new(mem, `\\server\foo\.`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\`); + assert(path::new(mem, `\\server\foo\`, path_env: PathEnv.WIN32).str_view()!! == `\\server\foo\`); + +} + +fn void test_extension() => mem::@scoped(allocator::temp()) +{ + assert(@catch(path::new(mem, `C:`, path_env: PathEnv.WIN32).extension())); + assert(@catch(path::new(mem, `C:`, path_env: PathEnv.POSIX).extension())); + assert(@catch(path::new(mem, `file`, path_env: PathEnv.WIN32).extension())); + assert(@catch(path::new(mem, `file`, path_env: PathEnv.POSIX).extension())); + assert(@catch(path::new(mem, `C:\temp\foo.bar\README`, path_env: PathEnv.WIN32).extension())); + + assert(path::for_windows(mem, "file.txt").extension()!! == "txt"); + assert(path::for_posix(mem, "file.txt").extension()!! == "txt"); + + assert(path::for_windows(mem, "a/b/file.txt").extension()!! == "txt"); + assert(path::for_posix(mem, "a/b/file.txt").extension()!! == "txt"); + + assert(path::for_windows(mem, "a\\b\\file.txt").extension()!! == "txt"); + + assert(path::for_windows(mem, "a.b/file.txt").extension()!! == "txt"); + assert(path::for_posix(mem, "a.b/file.txt").extension()!! == "txt"); + assert(path::for_windows(mem, "a.b/file.txt").extension()!! == "txt"); + assert(path::for_posix(mem, "a.b/file.txt").extension()!! == "txt"); + + assert(path::for_windows(mem, "a.b\\file.txt").extension()!! == "txt"); + + assert(path::for_windows(mem, "domain.dot.com").extension()!! == "com"); + assert(path::for_posix(mem, "domain.dot.com").extension()!! == "com"); + + assert(path::for_windows(mem, "image.jpeg").extension()!! == "jpeg"); + assert(path::for_posix(mem, "image.jpeg").extension()!! == "jpeg"); + + assert(path::for_windows(mem, "../filename.ext").extension()!! == "ext"); + assert(path::for_posix(mem, "../filename.ext").extension()!! == "ext"); + +} + +fn void test_has_extension() => mem::@scoped(allocator::temp()) +{ + assert(!path::new(mem, `C:\temp\foo.bar\README`, path_env: PathEnv.WIN32)!!.has_extension(`bar\README`)); + + assert(path::for_windows(mem, "file.txt")!!.has_extension("txt")); + assert(path::for_posix(mem, "file.txt")!!.has_extension("txt")); + + assert(path::for_windows(mem, "a/b/file.txt")!!.has_extension("txt")); + assert(path::for_posix(mem, "a/b/file.txt")!!.has_extension("txt")); + + assert(path::for_windows(mem, "a\\b\\file.txt")!!.has_extension("txt")); + + assert(path::for_windows(mem, "a.b/file.txt")!!.has_extension("txt")); + assert(path::for_posix(mem, "a.b/file.txt")!!.has_extension("txt")); + assert(path::for_windows(mem, "a.b/file.txt")!!.has_extension("txt")); + assert(path::for_posix(mem, "a.b/file.txt")!!.has_extension("txt")); + + assert(path::for_windows(mem, "a.b\\file.txt")!!.has_extension("txt")); + + assert(path::for_windows(mem, "domain.dot.com")!!.has_extension("com")); + assert(path::for_posix(mem, "domain.dot.com")!!.has_extension("com")); + + assert(path::for_windows(mem, "image.jpeg")!!.has_extension("jpeg")); + assert(path::for_posix(mem, "image.jpeg")!!.has_extension("jpeg")); + + assert(path::for_windows(mem, "../filename.ext")!!.has_extension("ext")); + assert(path::for_posix(mem, "../filename.ext")!!.has_extension("ext")); + +} + +fn void test_basename() => mem::@scoped(allocator::temp()) +{ + assert(path::for_windows(mem, "file.txt").basename()!! == "file.txt"); + assert(path::for_posix(mem, "file.txt").basename()!! == "file.txt"); + + assert(path::for_windows(mem, "a/b/file.txt").basename()!! == "file.txt"); + assert(path::for_posix(mem, "a/b/file.txt").basename()!! == "file.txt"); + + assert(path::for_windows(mem, "a.b/file.txt").basename()!! == "file.txt"); + assert(path::for_posix(mem, "a.b/file.txt").basename()!! == "file.txt"); + + assert(path::for_windows(mem, "a.b/file.txt").basename()!! == "file.txt"); + assert(path::for_posix(mem, "a.b/file.txt").basename()!! == "file.txt"); + + assert(path::for_windows(mem, "../filename.ext").basename()!! == "filename.ext"); + assert(path::for_posix(mem, "../filename.ext").basename()!! == "filename.ext"); + + assert(path::for_windows(mem, "C:").basename()!! == ""); + assert(path::for_posix(mem, "C:").basename()!! == "C:"); + + assert(path::for_windows(mem, "../..").basename()!! == ".."); + assert(path::for_posix(mem, "../..").basename()!! == ".."); + + assert(path::for_windows(mem, `\\server\abc`).basename()!! == ""); + assert(path::for_posix(mem, `\\server\abc`).basename()!! == `\\server\abc`); +} + +fn void test_dirname() => mem::@scoped(allocator::temp()) +{ + assert(path::for_posix(mem, "").dirname()!! == "."); + assert(path::for_posix(mem, "/file").dirname()!! == "/"); + assert(path::for_posix(mem, "///").dirname()!! == "/"); + assert(path::for_windows(mem, "d:").dirname()!! == "d:"); + assert(path::for_windows(mem, "d:file").dirname()!! == "d:"); + assert(path::for_windows(mem, `d:\file`).dirname()!! == `d:\`); + + assert(path::for_windows(mem, "file.txt").dirname()!! == "."); + assert(path::for_posix(mem, "file.txt").dirname()!! == "."); + + assert(path::for_windows(mem, "a/b/file.txt").dirname()!! == `a\b`); + assert(path::for_posix(mem, "a/b/file.txt").dirname()!! == "a/b"); + + assert(path::for_windows(mem, "a.b/file.txt").dirname()!! == "a.b"); + assert(path::for_posix(mem, "a.b/file.txt").dirname()!! == "a.b"); + + assert(path::for_windows(mem, "../filename.ext").dirname()!! == ".."); + assert(path::for_posix(mem, "../filename.ext").dirname()!! == ".."); + + assert(path::for_windows(mem, "C:").dirname()!! == "C:"); + assert(path::for_posix(mem, "C:").dirname()!! == "."); + + assert(path::for_windows(mem, "C:/").dirname()!! == "C:\\"); + assert(path::for_posix(mem, "C:/").dirname()!! == "."); + + assert(path::for_windows(mem, "C:/a").dirname()!! == "C:\\"); + assert(path::for_posix(mem, "C:/a").dirname()!! == "C:"); + + assert(path::for_windows(mem, "../..").dirname()!! == ".."); + assert(path::for_posix(mem, "../..").dirname()!! == ".."); + + assert(path::for_windows(mem, `\\server\share\dir\file`).dirname()!! == `\\server\share\dir`); + assert(path::for_windows(mem, `\\server\share\file`).dirname()!! == `\\server\share`); + assert(path::for_windows(mem, `\\server\share\`).dirname()!! == `\\server\share`); + assert(path::for_windows(mem, `\\server\share`).dirname()!! == `\\server\share`); + assert(path::for_posix(mem, `\\server\`).dirname()!! == `.`); +} + +fn void test_path_volume() => mem::@scoped(allocator::temp()) +{ + assert(path::for_windows(mem, `C:\abs`).volume_name()!! == `C:`); + assert(path::for_windows(mem, `C:abs`).volume_name()!! == `C:`); + assert(path::for_posix(mem, `C:/abs`).volume_name()!! == ``); + assert(path::for_posix(mem, `C:abs`).volume_name()!! == ``); + assert(path::for_windows(mem, `\\server\foo`).volume_name()!! == `\\server\foo`); + assert(path::for_windows(mem, `\\server\foo\abc`).volume_name()!! == `\\server\foo`); +} + +fn void test_path_is_absolute() => mem::@scoped(allocator::temp()) +{ + assert(!path::for_posix(mem, "").is_absolute()!!); + assert(path::for_posix(mem, "/").is_absolute()!!); + assert(path::for_posix(mem, "/a/b").is_absolute()!!); + assert(!path::for_posix(mem, "a/b").is_absolute()!!); + + assert(!path::for_windows(mem, `C:`).is_absolute()!!); + assert(path::for_windows(mem, `C:\abs`).is_absolute()!!); + assert(!path::for_windows(mem, `C:abs`).is_absolute()!!); + assert(path::for_windows(mem, `\\server\foo`).is_absolute()!!); + assert(path::for_windows(mem, `\\server\foo\abc`).is_absolute()!!); +} + +fn void test_path_absolute() => mem::@scoped(allocator::temp()) +{ +$if env::WIN32: + assert(path::for_windows(mem, `C:\abs`).absolute(mem, )!!.str_view() == `C:\abs`); +$else + assert(path::for_posix(mem, "/").absolute(mem, )!!.str_view() == "/"); + assert(path::for_posix(mem, ".").absolute(mem, )!!.str_view() == path::tcwd()!!.str_view()); +$endif +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/printf.c3 b/test/unit7/stdlib/io/printf.c3 new file mode 100644 index 000000000..263643a0d --- /dev/null +++ b/test/unit7/stdlib/io/printf.c3 @@ -0,0 +1,116 @@ +module std::io @test; + +fn void printf_int() +{ + String s; + s = string::format(mem, "[%-10d]", 78); + assert(s == "[78 ]"); + free(s); + s = string::format(mem, "[%10d]", 78); + assert(s == "[ 78]"); + free(s); + s = string::format(mem, "[%010d]", 78); + assert(s == "[0000000078]"); + free(s); + s = string::format(mem, "[%+10d]", 78); + assert(s == "[ +78]"); + free(s); + s = string::format(mem, "[%-+10d]", 78); + assert(s == "[+78 ]"); + free(s); +} + +fn void printf_a() +{ + String s; + s = string::format(mem, "%08.2a", 234.125); + assert(s == "0x1.d4p+7", "got '%s'; want '0x1.d4p+7'", s); + free(s); + s = string::format(mem, "%a", 234.125); + assert(s == "0x1.d44p+7", "got '%s'; want '0x1.d44p+7'", s); + free(s); + s = string::format(mem, "%A", 234.125); + assert(s == "0X1.D44P+7", "got '%s'; want '0X1.D44P+7'", s); + free(s); + s = string::format(mem, "%20a", 234.125); + assert(s == " 0x1.d44p+7", "got '%s'; want ' 0x1.d44p+7'", s); + free(s); + s = string::format(mem, "%-20a", 234.125); + assert(s == "0x1.d44p+7 ", "got '%s'; want '0x1.d44p+7 '", s); + free(s); + s = string::format(mem, "%-20s", "hello world"); + assert(s == "hello world ", "got '%s'; want 'hello world '", s); + free(s); + s = string::format(mem, "%20s", "hello world"); + assert(s == " hello world", "got '%s'; want ' hello world'", s); + free(s); + + String str = "hello!"; + s = string::format(mem, "%-20s", str); + assert(s == "hello! ", "got '%s'; want 'hello! '", s); + free(s); + s = string::format(mem, "%20s", str); + assert(s == " hello!", "got '%s'; want ' hello!'", s); + free(s); + + int[2] a = { 12, 23 }; + s = string::format(mem, "%-20s", a); + assert(s == "[12, 23] ", "got '%s'; want '[12, 23] '", s); + free(s); + s = string::format(mem, "%20s", a); + assert(s == " [12, 23]", "got '%s'; want ' [12, 23]'", s); + free(s); + + s = string::format(mem, "%-20s", a[..]); + assert(s == "[12, 23] ", "got '%s'; want '[12, 23] '", s); + free(s); + s = string::format(mem, "%20s", a[..]); + assert(s == " [12, 23]", "got '%s'; want ' [12, 23]'", s); + free(s); + + float[2] f = { 12.0, 23.0 }; + s = string::format(mem, "%-24s", f); + assert(s == "[12.000000, 23.000000] ", "got '%s'; want '[12.000000, 23.000000] '", s); + free(s); + s = string::format(mem, "%24s", f); + assert(s == " [12.000000, 23.000000]", "got '%s'; want ' [12.000000, 23.000000]'", s); + free(s); + + int[<2>] vec = { 12, 23 }; + s = string::format(mem, "%-20s", vec); + assert(s == "[<12, 23>] ", "got '%s'; want '[<12, 23>] '", s); + free(s); + s = string::format(mem, "%20s", vec); + assert(s == " [<12, 23>]", "got '%s'; want ' [<12, 23>]'", s); + free(s); + + String ss = "hello world"; + s = string::format(mem, "%.4s %.5s", ss, ss); + assert(s == "hell hello", "got '%s'; want 'hell hello'", s); + free(s); +} + +enum PrintfTest : ushort +{ + ENUMA, + ENUMB, +} + +fn void printf_enum() +{ + String s; + + s = string::format(mem, "%s", PrintfTest.ENUMA); + assert(s == "ENUMA", "got '%s'; want 'ENUMA'", s); + free(s); + s = string::format(mem, "%s", PrintfTest.ENUMB); + assert(s == "ENUMB", "got '%s'; want 'ENUMB'", s); + free(s); + + s = string::format(mem, "%d", PrintfTest.ENUMA); + assert(s == "0", "got '%s'; want '0'", s); + free(s); + s = string::format(mem, "%d", PrintfTest.ENUMB); + assert(s == "1", "got '%s'; want '1'", s); + free(s); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/scanner.c3 b/test/unit7/stdlib/io/scanner.c3 new file mode 100644 index 000000000..5a6582dcc --- /dev/null +++ b/test/unit7/stdlib/io/scanner.c3 @@ -0,0 +1,68 @@ +module std::io @test; +import std::collections::list; + +def Results = List{String}; + +struct ScanTest +{ + String in; + String[] out; + String left_over; +} + +fn void scanner() +{ + ScanTest[] tcases = { + {"aa,,bb", {"aa"}, "bb"}, + {"a,,b,,", {"a", "b"}, ""}, + {"ab,,c", {"ab"}, "c"}, + {"ab,,cd,,e", {"ab", "cd"}, "e"}, + }; + foreach (tc : tcases) + { + ByteReader br; + br.init(tc.in); + Scanner sc; + char[4] buffer; // max match (2) + pattern length (2) + sc.init(&br, buffer[..]); + + Results results; + defer results.free(); + while LOOP: (true) + { + char[]! res = sc.scan(",,"); + if (catch err = res) + { + case SearchResult.MISSING: + break LOOP; + default: + return err?!!; + } + String str = (String)res; + results.push(str.tconcat("")); + } + + String[] got = results.array_view(); + assert(got == tc.out, "got %s; want %s", got, tc.out); + char[] fl = sc.flush(); + String left_over = (String)fl; + assert(left_over == tc.left_over, "%s -> %s", tc.in, left_over); + } +} + +fn void scanner_as_reader() +{ + ByteReader br; + br.init("Lorem ipsum sit."); + Scanner sc; + char[8] buffer; + sc.init(&br, buffer[..]); + + sc.scan(" ")!!; + + char[16] res; + usz n = sc.read(&res)!!; + String str = (String)res[:n]; + + assert(str == "ipsum sit.", "got '%s'; want 'ipsum sit.'", str); +} \ No newline at end of file diff --git a/test/unit7/stdlib/io/stream.c3 b/test/unit7/stdlib/io/stream.c3 new file mode 100644 index 000000000..14e960d66 --- /dev/null +++ b/test/unit7/stdlib/io/stream.c3 @@ -0,0 +1,89 @@ +module std::io @test; + +fn void read_ushort_test() +{ + ByteReader reader = io::wrap_bytes({0x34, 0x8a}); + assert(io::read_be_ushort(&reader)!! == 0x348a); +} + +fn void read_uint_test() +{ + ByteReader reader = io::wrap_bytes({0x34, 0x8a, 0xef, 0xcc}); + assert(io::read_be_uint(&reader)!! == 0x348aefcc); +} + +fn void read_ulong_test() +{ + ByteReader reader = io::wrap_bytes({0x34, 0x8a, 0xef, 0xcc, 0x34, 0x8a, 0xef, 0xcc}); + assert(io::read_be_ulong(&reader)!! == 0x348aefcc348aefcc); +} + +fn void read_uint128_test() +{ + ByteReader reader = io::wrap_bytes({0x34, 0x8a, 0xef, 0xcc, 0x34, 0x8a, 0xef, 0xcc, 0x34, 0x8a, 0xef, 0xcc, 0x34, 0x8a, 0xef, 0xcc}); + assert(io::read_be_uint128(&reader)!! == 0x348aefcc348aefcc348aefcc348aefcc); +} + +fn void write_ushort_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_be_short(&bw, 0x348a)!!; + assert(bw.str_view() == &&x'348a'); +} + +fn void write_uint_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_be_int(&bw, 0x3421348a)!!; + assert(bw.str_view() == &&x'3421348a'); +} + +fn void write_ulong_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_be_long(&bw, 0xaabbccdd3421348a)!!; + assert(bw.str_view() == &&x'aabbccdd3421348a'); +} + +fn void write_uint128_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_be_int128(&bw, 0xaabbccdd3421348aaabbccdd3421348a)!!; + assert(bw.str_view() == &&x'aabbccdd3421348aaabbccdd3421348a'); +} + +fn void write_tiny_bytearray_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_tiny_bytearray(&bw, &&x"aabbcc00112233")!!; + assert(bw.str_view() == &&x'07aabbcc00112233'); +} + +fn void write_short_bytearray_test() +{ + ByteWriter bw; + bw.temp_init(); + io::write_short_bytearray(&bw, &&x"aabbcc00112233")!!; + assert(bw.str_view() == &&x'0007aabbcc00112233'); +} + +fn void read_tiny_bytearray_test() +{ + ByteReader reader = io::wrap_bytes(&&x'07aabbcc00112233'); + char[] read = io::read_tiny_bytearray(&reader, allocator: allocator::heap())!!; + assert(read == &&x'aabbcc00112233'); + free(read); +} + +fn void read_short_bytearray_test() +{ + ByteReader reader = io::wrap_bytes(&&x'0007aabbcc00112233'); + char[] read = io::read_short_bytearray(&reader, allocator: allocator::heap())!!; + assert(read == &&x'aabbcc00112233'); + free(read); +} diff --git a/test/unit7/stdlib/io/teereader.c3 b/test/unit7/stdlib/io/teereader.c3 new file mode 100644 index 000000000..c76181f5f --- /dev/null +++ b/test/unit7/stdlib/io/teereader.c3 @@ -0,0 +1,17 @@ +module std::io @test; + +fn void test_teereader() +{ + String want = "foobar"; + + ByteWriter w; + TeeReader r = tee_reader((ByteReader){}.init(want), w.temp_init()); + + char[16] buf; + usz n = r.read(buf[..])!!; + + String got = w.str_view(); + assert(n == want.len, "teereader: invalid length"); + assert(got == want, "teereader: got: %s, want: %s", got, want); + assert(got == (String)buf[:n], "teereader: got: %s, want: %s", got, (String)buf[:n]); +} diff --git a/test/unit7/stdlib/io/varint.c3 b/test/unit7/stdlib/io/varint.c3 new file mode 100644 index 000000000..391f04b12 --- /dev/null +++ b/test/unit7/stdlib/io/varint.c3 @@ -0,0 +1,54 @@ +module std::io::varint @test; +import std::io; + +fn void write_read() +{ + ByteBuffer buf; + buf.temp_init(16); + usz n; + uint x; + uint y; + + n = io::write_varint(&buf, 123)!!; + assert(n == 1, "got %d; want 1", n); + io::read_varint(&buf, &y)!!; + assert(y == 123, "got %d; want 123", y); + + n = io::write_varint(&buf, 123456789)!!; + assert(n == 4, "got %d; want 4", n); + io::read_varint(&buf, &y)!!; + assert(y == 123456789, "got %d; want 123456789", y); +} + +struct VarIntTest +{ + uint in; + char[] bytes; +} + +fn void samples() +{ + VarIntTest[] tcases = { + { 0, { 0x00 } }, + { 100, { 0x64 } }, + { 127, { 0x7F } }, + { 128, { 0x80, 0x01 } }, + { 16271, { 0x8F, 0x7F } }, + { 16383, { 0xFF, 0x7F } }, + { 16384, { 0x80, 0x80, 0x01 } }, + { 1048576, { 0x80, 0x80, 0x40 } }, + { 2097151, { 0xFF, 0xFF, 0x7F } }, + { 2097152, { 0x80, 0x80, 0x80, 0x01 } }, + { 2147483648, { 0x80, 0x80, 0x80, 0x80, 0x08 } }, + { 4294967295, { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F } }, + }; + foreach (tc : tcases) + { + ByteWriter bw; + bw.temp_init(); + usz n = io::write_varint(&bw, tc.in)!!; + assert(n == tc.bytes.len, "got %d; want %d", n, tc.bytes.len); + char[] bytes = bw.bytes[:bw.index]; + assert(bytes == tc.bytes, "got %d; want %d", bytes, tc.bytes); + } +} \ No newline at end of file diff --git a/test/unit7/stdlib/macros/core_builtins.c3 b/test/unit7/stdlib/macros/core_builtins.c3 new file mode 100644 index 000000000..64537fcba --- /dev/null +++ b/test/unit7/stdlib/macros/core_builtins.c3 @@ -0,0 +1,23 @@ +module core_builtin_tests; + +fn void test_likely() @test +{ + assert(@likely(2 > 1)); + assert(@likely(2 > 1, 0.5)); +} + +fn void test_unlikely() @test +{ + assert(!@unlikely(2 < 1)); + assert(!@unlikely(2 < 1, 0.5)); +} + +fn void test_expect() @test +{ + assert(@expect(2 > 1, true)); + assert(!@expect(2 < 1, false)); + + assert(@expect(2 > 1, true, 0.5)); + assert(!@expect(2 < 1, false, 0.5)); +} + diff --git a/test/unit7/stdlib/math/bigint.c3 b/test/unit7/stdlib/math/bigint.c3 new file mode 100644 index 000000000..41b21f718 --- /dev/null +++ b/test/unit7/stdlib/math/bigint.c3 @@ -0,0 +1,75 @@ +module std::math::bigint @test; + +fn void test_parse16() +{ + BigInt bi @noinit; + assert(bi.init_string_radix("c", 16)!!.equals(bigint::from_int(12))); +} + +fn void test_zero() +{ + assert(bigint::from_int(0).to_string(allocator::temp()) == "0"); + BigInt bi; + bi.init_string_radix("00", 16)!!; + assert(bi.to_string(allocator::temp()) == "0"); +} + +fn void test_plus() +{ + BigInt a = bigint::from_int(123); + BigInt b = bigint::from_int(234); + assert(a.add(b).equals(bigint::from_int(234 + 123))); + + a = bigint::from_int(12323400012311213314141414i128); + b = bigint::from_int(23400012311213314141414i128); + assert(a.add(b).equals(bigint::from_int(12323400012311213314141414i128 + 23400012311213314141414i128))); +} + +fn void test_mult() +{ + BigInt a = bigint::from_int(123); + BigInt b = bigint::from_int(234); + assert(a.mult(b).equals(bigint::from_int(234 * 123))); + + a = bigint::from_int(1232311213314141414i128); + b = bigint::from_int(234000123112414i128); + assert(a.mult(b).equals(bigint::from_int(1232311213314141414i128 * 234000123112414i128))); +} + +fn void test_minus() +{ + BigInt a = bigint::from_int(123); + BigInt b = bigint::from_int(234); + assert(a.sub(b).equals(bigint::from_int(123 - 234))); + + a = bigint::from_int(12323400012311213314141414i128); + b = bigint::from_int(23400012311213314141414i128); + assert(a.sub(b).equals(bigint::from_int(12323400012311213314141414i128 - 23400012311213314141414i128))); +} + +fn void test_init_string_radix() +{ + BigInt a; + a.init_string_radix("123", 10)!!; + assert(a.equals(bigint::from_int(123))); + a.init_string_radix("123", 8)!!; + assert(a.equals(bigint::from_int(0o123))); + a.init_string_radix("123", 16)!!; + assert(a.equals(bigint::from_int(0x123))); +} + +fn void test_gcd() +{ + BigInt a = bigint::from_int(15); + BigInt b = bigint::from_int(20); + assert(a.gcd(b).equals(bigint::from_int(5))); + assert(math::gcd(a,b).equals(bigint::from_int(5))); +} + +fn void test_lcm() +{ + BigInt a = bigint::from_int(11); + BigInt b = bigint::from_int(17); + assert(a.lcm(b).equals(bigint::from_int(11*17))); + assert(math::lcm(a,b).equals(bigint::from_int(11*17))); +} diff --git a/test/unit7/stdlib/math/frexp_signbit.c3 b/test/unit7/stdlib/math/frexp_signbit.c3 new file mode 100644 index 000000000..7f6473e4b --- /dev/null +++ b/test/unit7/stdlib/math/frexp_signbit.c3 @@ -0,0 +1,18 @@ +module std::math @test; + +fn void test_frexp() +{ + int a; + double z = math::frexp(231.23, &a); + assert((z - 0.903242187) < 0.0000001 && a == 8); + float z2 = math::frexp(231.23f, &a); + assert((z2 - 0.903242187) < 0.0000001 && a == 8); +} + +fn void test_signbit() +{ + assert(math::signbit(-231.3) == 1); + assert(math::signbit(231.3) == 0); + assert(math::signbit(float.inf) == 0); + assert(math::signbit(-float.inf) == 1); +} diff --git a/test/unit7/stdlib/math/math.c3 b/test/unit7/stdlib/math/math.c3 new file mode 100644 index 000000000..3e1a7f1fd --- /dev/null +++ b/test/unit7/stdlib/math/math.c3 @@ -0,0 +1,614 @@ +module math_tests; +import std::math; + +fn void test_abs() @test +{ + int x = -21; + assert(math::abs(x) == 21); + double y = -123.0; + assert(math::abs(y) == 123.0); + float z = -21.0f; + assert(math::abs(z) == 21.0f); + $assert @typeis(math::abs(z), float); + int[<3>] xx = { -1, -1000, 1000 }; + assert(math::abs(xx) == (int[<3>]) { 1, 1000, 1000 }); + double[<3>] yy = { -1, -0.5, 1000 }; + assert(math::abs(yy) == (double[<3>]) { 1, 0.5, 1000 }); +} + +fn void test_acos() @test +{ + int [<5>] in = { 231, -231, 1, 0, -1 }; + double [<3>] out = { 0., math::PI_2, math::PI }; + double [<6>] in2 = { 0.9, 0.6, 0.1, -0.1, -0.6, -0.9 }; + double [<6>] out2 = { 0.45102681179626236, 0.9272952180016123, 1.4706289056333368, 1.6709637479564565, 2.214297435588181, 2.6905658417935308 }; + assert(@typeis(math::acos(in[0]), double)); + assert(@typeis(math::acos((float)in[0]), float)); + assert(@typeis(math::acos((double)in[0]), double)); + for (int i = 0; i < 2; i++) + { + double x = math::acos(in[i]); + assert(math::is_nan(x), "acos(%d)=%f is not nan", in[i], x); + float f = math::acos((float)in[i]); + assert(math::is_nan(f), "acos(%f)=%f is not nan", in[i], f); + x = math::acos((double)in[i]); + assert(math::is_nan(x), "acos(%f)=%f is not nan", in[i], x); + } + for (int i = 2; i < 5; i++) + { + int ii = i-2; + double x = math::acos(in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "acos(%d)=%f is not equal to %f", in[i], x, out[ii]); + float f = math::acos((float)in[i]); + assert(math::is_approx_rel(f, (float)out[ii], 1e-6), "acos(%f)=%f is not equal to %f", in[i], f, out[ii]); + x = math::acos((double)in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "acos(%f)=%f is not equal to %f", in[i], x, out[ii]); + } + for (int i = 0; i < 6; i++) + { + float f = math::acos((float)in2[i]); + assert(math::is_approx(f, (float)out2[i], 1e-6), "acos(%f)=%f is not equal to %f", (float)in2[i], f, (float)out2[i]); + double x = math::acos(in2[i]); + assert(math::is_approx(x, out2[i], 1e-12), "acos(%f)=%f is not equal to %f", in2[i], x, out2[i]); + } +} + + +fn void test_acosh() @test +{ + int [<5>] in = { 0, -1, 1, 2, 231 }; + double [<3>] out = { 0., 1.3169578969248166, 6.135560205979194 }; + assert(@typeis(math::acosh(in[0]), double)); + assert(@typeis(math::acosh((float)in[0]), float)); + assert(@typeis(math::acosh((double)in[0]), double)); + for (int i = 0; i < 2; i++) + { + assert(math::is_nan(math::acosh(in[i])), "acosh(%d)=%f is not nan", in[i]); + assert(math::is_nan(math::acosh((float)in[i])), "acosh(%f) is not nan", in[i]); + assert(math::is_nan(math::acosh((double)in[i])), "acosh(%f) is not nan", in[i]); + } + for (int i = 2; i < 5; i++) + { + int ii = i-2; + double x = math::acosh(in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "acosh(%d)=%f is not equal to %f", in[i], x, out[ii]); + float f = math::acosh((float)in[i]); + assert(math::is_approx_rel(f, (float)out[ii], 1e-6), "acosh(%f)=%f is not equal to %f", in[i], f, out[ii]); + x = math::acosh((double)in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "acosh(%f)=%f is not equal to %f", in[i], x, out[ii]); + } +} + +fn void test_asin() @test +{ + int [<5>] in = { 231, -231, 1, 0, -1 }; + double [<3>] out = { math::PI_2, 0., -math::PI_2 }; + double [<6>] in2 = { 0.98, 0.6, 0.1, -0.1, -0.6, -0.98 }; + double [<6>] out2 = { 1.3704614844717768, 0.6435011087932844, 0.1001674211615598, -0.1001674211615598, -0.6435011087932844, -1.3704614844717768 }; + assert(@typeis(math::asin(in[0]), double)); + assert(@typeis(math::asin((float)in[0]), float)); + assert(@typeis(math::asin((double)in[0]), double)); + for (int i = 0; i < 2; i++) + { + assert(math::is_nan(math::asin(in[i])), "asin(%d)=%f is not nan", in[i]); + assert(math::is_nan(math::asin((float)in[i])), "asin(%f) is not nan", in[i]); + assert(math::is_nan(math::asin((double)in[i])), "asin(%f) is not nan", in[i]); + } + for (int i = 2; i < 5; i++) + { + int ii = i-2; + double x = math::asin(in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "asin(%d)=%f is not equal to %f", in[i], x, out[ii]); + float f = math::asin((float)in[i]); + assert(math::is_approx_rel(f, (float)out[ii], 1e-6), "asin(%f)=%f is not equal to %f", in[i], f, out[ii]); + x = math::asin((double)in[i]); + assert(math::is_approx_rel(x, out[ii], 1e-12), "asin(%f)=%f is not equal to %f", in[i], x, out[ii]); + } + for (int i = 0; i < 6; i++) + { + float f = math::asin((float)in2[i]); + assert(math::is_approx(f, (float)out2[i], 1e-6), "asin(%f)=%f is not equal to %f", (float)in2[i], f, (float)out2[i]); + double x = math::asin(in2[i]); + assert(math::is_approx(x, out2[i], 1e-12), "asin(%f)=%f is not equal to %f", in2[i], x, out2[i]); + } +} + +fn void test_asinh() @test +{ + int [<5>] in = { 231, 1, 0, -1, -231 }; + double [<5>] out = { 6.135569576118435, 0.881373587019543, 0., -0.881373587019543, -6.135569576118435 }; + assert(@typeis(math::asinh(in[0]), double)); + assert(@typeis(math::asinh((float)in[0]), float)); + assert(@typeis(math::asinh((double)in[0]), double)); + for (int i = 0; i < 5; i++) + { + double x = math::asinh(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "asinh(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::asinh((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "asinh(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::asinh((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "asinh(%f)=%f is not equal to %f", in[i], x, out[i]); + } +} + +fn void test_atan() @test +{ + int [<9>] in = { 231, 3, 2, 1, 0, -1, -2, -3, -231 }; + double [<9>] out = { 1.5664673495078372, 1.2490457723982544, 1.1071487177940904, math::PI_4, 0., -math::PI_4, -1.1071487177940904, -1.2490457723982544, -1.5664673495078372 }; + double [<6>] in2 = { 0.6, 0.4, 0.1, -0.1, -0.4, -0.6 }; + double [<6>] out2 = { 0.5404195002705842, 0.3805063771123649, 0.09966865249116204, -0.09966865249116204, -0.3805063771123649, -0.5404195002705842 }; + assert(@typeis(math::atan(in[0]), double)); + assert(@typeis(math::atan((float)in[0]), float)); + assert(@typeis(math::atan((double)in[0]), double)); + for (int i = 0; i < 9; i++) + { + double x = math::atan(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "atan(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::atan((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "atan(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::atan((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "atan(%f)=%f is not equal to %f", in[i], x, out[i]); + } + for (int i = 0; i < 6; i++) + { + float f = math::atan((float)in2[i]); + assert(math::is_approx(f, (float)out2[i], 1e-6), "atan(%f)=%f is not equal to %f", (float)in2[i], f, (float)out2[i]); + double x = math::atan(in2[i]); + assert(math::is_approx(x, out2[i], 1e-12), "atan(%f)=%f is not equal to %f", in2[i], x, out2[i]); + } +} + +fn void test_atanh() @test +{ + int [<4>] in = { 231, -231, 1, -1 }; + double [<6>] in2 = {0.8, 0.5, 0.3, -0.3, -0.5, -0.8 }; + double [<6>] out = { 1.0986122886681098, 0.5493061443340548, 0.30951960420311175, -0.30951960420311175, -0.5493061443340548, -1.0986122886681098 }; + assert(@typeis(math::atanh(in[0]), double)); + assert(@typeis(math::atanh((float)in[0]), float)); + assert(@typeis(math::atanh((double)in[0]), double)); + for (int i = 0; i < 2; i++) + { + assert(math::is_nan(math::atanh(in[i])), "atanh(%d) is not nan", in[i]); + assert(math::is_nan(math::atanh((float)in[i])), "atanh(%f) is not nan", in[i]); + assert(math::is_nan(math::atanh((double)in[i])), "atanh(%f) is not nan", in[i]); + } + for (int i = 2; i < 4; i++) + { + assert(math::is_inf(math::atanh(in[i])), "atanh(%d)=%f is not inf", in[i]); + assert(math::is_inf(math::atanh((float)in[i])), "atanh(%f) is not inf", in[i]); + assert(math::is_inf(math::atanh((double)in[i])), "atanh(%f) is not inf", in[i]); + } + assert(math::atanh(0) == 0., "atanh(%d) is not equal to %f", 0, 0.); + assert(math::atanh(0.f) == 0.f, "atanh(%f) is not equal to %f", 0.f, 0.f); + assert(math::atanh(0.) == 0., "atanh(%f) is not equal to %f", 0., 0.); + for (int i = 0; i < 6; i++) + { + float f = math::atanh((float)in2[i]); + assert(math::is_approx(f, (float)out[i], 1e-6), "atanh(%f)=%f is not equal to %f", in2[i], f, out[i]); + double x = math::atanh((double)in2[i]); + assert(math::is_approx(x, out[i], 1e-12), "atanh(%f)=%f is not equal to %f", in2[i], x, out[i]); + } +} + +fn void test_floating_point_word() @test +{ + float f = 1.f; + assert(f.word() == 0x3f800000); + // xor swap + float f1 = 2.f; + float f2 = 3.f; + uint u1 = f1.word(); + uint u2 = f2.word(); + u1 ^= u2; u2 ^= u1; u1 ^= u2; + f1.set_word(u1); + f2.set_word(u2); + assert((f1 == 3.f) && (f2 == 2.f)); + // sign bit trick + f = -1.f; + assert((f.word() >> 31) == 1); + f = 1.f; + assert((f.word() >> 31) == 0); + // absolute value bit trick + float[<4>] fvals = { 1.f, -1.f, 91.5f, -91.5f }; + for (int i = 0; i < 4; i++) + { + f = fvals[i]; + f.set_word(f.word() & 0x7fffffff); + assert(f == math::abs(fvals[i])); + } + + double d = 1.; + assert((d.high_word() - 0x3ff00000 | d.low_word()) == 0); + // xor swap + double d1 = 2.0; + double d2 = 3.0; + uint u1_low = d1.low_word(); + uint u2_low = d2.low_word(); + u1_low ^= u2_low; u2_low ^= u1_low; u1_low ^= u2_low; + uint u1_high = d1.high_word(); + uint u2_high = d2.high_word(); + u1_high ^= u2_high; u2_high ^= u1_high; u1_high ^= u2_high; + d1.set_low_word(u1_low); + d1.set_high_word(u1_high); + d2.set_low_word(u2_low); + d2.set_high_word(u2_high); + assert((d1 == 3.) && (d2 == 2.)); + // sign bit trick + d = -1.; + assert((d.high_word() >> 31) == 1); + d = 1.; + assert((d.high_word() >> 31) == 0); + // absolute value bit trick + double[<4>] vals = { 1., -1., 91.5, -91.5 }; + for (int i = 0; i < 4; i++) + { + d = vals[i]; + d.set_high_word(d.high_word() & 0x7fffffff); + assert(d == math::abs(vals[i])); + } +} + +fn void test_ceil() @test +{ + double d = -123.1; + assert(math::ceil(d) == -123.0); + d = 123.1; + assert(math::ceil(d) == 124.0); + d = 0.1; + assert(math::ceil(d) == 1); + d = -0.9; + assert(math::ceil(d) == 0); + $assert @typeis(math::ceil(d), double); + float f = -123.1f; + assert(math::ceil(f) == -123.0f); + f = 123.1f; + assert(math::ceil(f) == 124.0f); + f = 0.1f; + assert(math::ceil(f) == 1.0f); + f = -0.9f; + assert(math::ceil(f) == 0.0f); + $assert @typeis(math::ceil(f), float); + double[<5>] vec = { -123.1, 123.1, 0.1, -0.9, 0 }; + assert(math::ceil(vec) == (double[<5>]) { -123, 124, 1, 0, 0 }); +} + +fn void test_cos() @test +{ + int [<5>] in = { 231, 1, 0, -1, -231 }; + double [<5>] out = { 0.09280621889587707, 0.54030230586813972 , 1., 0.54030230586813972, 0.09280621889587707 }; + float [<2>] in2 = { math::PI, 0.f }; + float [<2>] out2 = { -1.f, 1.f }; + double [<2>] in3 = { math::PI, 0. }; + double [<2>] out3 = { -1., 1. }; + assert(@typeis(math::cos(in[0]), double)); + assert(@typeis(math::cos((float)in[0]), float)); + assert(@typeis(math::cos((double)in[0]), double)); + for (int i = 0; i < 5; i++) + { + double x = math::cos(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "cos(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::cos((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "cos(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::cos((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "cos(%f)=%f is not equal to %f", in[i], x, out[i]); + } + float [<2>] vecf = math::cos(in2); + double [<2>] vec = math::cos(in3); + for (int i = 0; i < 2; i++) + { + assert(math::is_approx_rel(vecf[i], out2[i], 1e-6), "cos(%f)=%f is not equal to %f", in2[i], vecf[i], out2[i]); + assert(math::is_approx_rel(vec[i], out3[i], 1e-12), "cos(%f)=%f is not equal to %f", in3[i], vec[i], out3[i]); + } +} + +fn void test_exp() @test +{ + int[<5>] in = { 2, 1, 0, -1, -2 }; + double[<5>] out = { 7.38905609893065, math::E , 1., 0.36787944117144233, 0.1353352832366127 }; + float[<6>] in2 = { 1.8f, 0.6f, 0.4f, -0.4f, -0.8f, -1.8f }; + float[<6>] out2 = {6.049647464412946f, 1.8221188003905089f, 1.4918246976412703f, 0.6703200460356393f, 0.44932896411722156f, 0.16529888822158656f }; + double[<6>] in3 = { 1.8, 0.6, 0.4, -0.4, -0.8, -1.8 }; + double[<6>] out3 = {6.049647464412946, 1.8221188003905089, 1.4918246976412703, 0.6703200460356393, 0.44932896411722156, 0.16529888822158656 }; + assert(@typeis(math::exp(in[0]), double)); + assert(@typeis(math::exp((float)in[0]), float)); + assert(@typeis(math::exp((double)in[0]), double)); + for (int i = 0; i < 5; i++) + { + double x = math::exp(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "exp(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::exp((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "exp(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::exp((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "exp(%f)=%f is not equal to %f", in[i], x, out[i]); + } + float[<6>] vecf = math::exp(in2); + double[<6>] vec = math::exp(in3); + for (int i = 0; i < 6; i++) + { + assert(math::is_approx_rel(vecf[i], out2[i], 1e-6), "exp(%f)=%f is not equal to %f", in2[i], vecf[i], out2[i]); + assert(math::is_approx_rel(vec[i], out3[i], 1e-12), "exp(%f)=%f is not equal to %f", in3[i], vec[i], out3[i]); + } +} + +fn void test_floor() @test +{ + double d = -123.1; + assert(math::floor(d) == -124.0); + d = 123.1; + assert(math::floor(d) == 123.0); + d = 0.9; + assert(math::floor(d) == 0); + d = -0.1; + assert(math::floor(d) == -1); + $assert @typeis(math::floor(d), double); + float f = -123.1f; + assert(math::floor(f) == -124.0f); + f = 123.1f; + assert(math::floor(f) == 123.0f); + f = 0.9f; + assert(math::floor(f) == 0.0f); + f = -0.1f; + assert(math::floor(f) == -1.0f); + $assert @typeis(math::floor(f), float); + double[<5>] vec = { -123.1, 123.1, 0.9, -0.1, 0 }; + assert(math::floor(vec) == (double[<5>]) { -124, 123, 0, -1, 0 }); +} + +fn void test_log() @test +{ + int[<8>] in = { 1, 10, 100, 1000, 1, 4, 8, 16 }; + double[<8>] out = { 0., 1., 2., 3., 0., 2. / 3., 1., 4. / 3. }; + float[<4>] bf = { 1.f / math::E, 1.f / (math::E * math::E), 1.f / (math::E * math::E), 1.f / math::E }; + float[<4>] in2 = { math::E * math::E, math::E, 1.f / math::E, 1.f / (math::E * math::E) }; + float[<4>] out2 = { -2.f, -0.5f, 0.5f, 2.f }; + double[<4>] bx = { 1. / math::E, 1. / (math::E * math::E), 1. / (math::E * math::E), 1. / math::E }; + double[<4>] in3 = { math::E * math::E, math::E, 1. / math::E, 1. / (math::E * math::E) }; + double[<4>] out3 = { -2., -0.5, 0.5, 2. }; + assert(@typeis(math::log(in[0], in[0]), double)); + assert(@typeis(math::log(in[0], (float)in[0]), float)); + assert(@typeis(math::log((float)in[0], in[0]), float)); + assert(@typeis(math::log(in[0], (double)in[0]), double)); + assert(@typeis(math::log((double)in[0], in[0]), double)); + assert(@typeis(math::log((float)in[0], (float)in[0]), float)); + assert(@typeis(math::log((float)in[0], (double)in[0]), double)); + assert(@typeis(math::log((double)in[0], (float)in[0]), double)); + assert(@typeis(math::log((double)in[0], (double)in[0]), double)); + for (int i = 0; i < 8; i++) + { + int base = (i < 4) ? 10 : 8; + + double x = math::log(in[i], base); + assert(math::is_approx_rel(x, out[i], 1e-12), "log(%d,%d)=%f is not equal to %f", in[i], base, x, out[i]); + + float f = math::log((float)in[i], base); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "log(%f,%d)=%f is not equal to %f", in[i], base, f, out[i]); + + x = math::log((double)in[i], base); + assert(math::is_approx_rel(x, out[i], 1e-12), "log(%f,%d)=%f is not equal to %f", in[i], base, x, out[i]); + } + float[<4>] vecf = math::log(in2, bf); + double[<4>] vec = math::log(in3, bx); + for (int i = 0; i < 4; i++) + { + assert(math::is_approx_rel(vecf[i], out2[i], 1e-6), "log(%f,%f)=%f is not equal to %f", in2[i], bf[i], vecf[i], out2[i]); + assert(math::is_approx_rel(vec[i], out3[i], 1e-12), "log(%f,%f)=%f is not equal to %f", in3[i], bx[i], vec[i], out3[i]); + } +} + +fn void test_pow() @test +{ + int[<10>] e = { 2, 1, 0, -1, -2, 2, 1, 0, -1, -2 }; + double[<10>] out = { 100., 10., 1., 0.1, 0.01, 4., 2., 1., 0.5, 0.25 }; + float[<2>] base2 = { -1.f / math::E, 1.f / math::E }; + float[<5>] out2 = { 1.f / (math::E * math::E), 1.f / math::E, 1.f, math::E, math::E * math::E }; + double[<2>] base3 = { -1. / math::E, 1. / math::E }; + double[<5>] out3 = { 1. / (math::E * math::E), 1. / math::E, 1., math::E, math::E * math::E }; + assert(@typeis(math::pow(e[1], e[1]), double)); + assert(@typeis(math::pow((float)e[1], e[1]), float)); + assert(@typeis(math::pow((double)e[1], e[1]), double)); + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 2; j++) + { + int base = (2 * j - 1) * ((i < 5) ? 10 : 2); + + double x = math::pow(base, e[i]); + double outx = (e[i] & 1) ? (double)(2 * j - 1) * out[i] : out[i]; + assert(math::is_approx_rel(x, outx, 1e-12), "pow(%d,%d)=%f is not equal to %f", base, e[i], x, outx); + + float f = math::pow((float)base, e[i]); + float outf = (e[i] & 1) ? (float)(2 * j - 1) * (float)out[i] : (float)out[i]; + assert(math::is_approx_rel(f, outf, 1e-6), "pow(%d,%f)=%f is not equal to %f", base, e[i], f, outf); + + x = math::pow((double)base, e[i]); + assert(math::is_approx_rel(x, outx, 1e-12), "pow(%d,%f)=%f is not equal to %f", base, e[i], x, outx); + } + } + for (int i = 0; i < 5; i++) + { + float[<2>] vecf = math::pow(base2, e[i]); + double[<2>] vec = math::pow(base3, e[i]); + for (int j = 0; j < 2; j++) + { + float outf = (e[i] & 1) ? (float)(2 * j - 1) * (float)out2[i] : (float)out2[i]; + assert(math::is_approx_rel(vecf[j], outf, 1e-6), "pow(%f,%f)=%f is not equal to %f", base2[i], e[i], vecf[j], outf); + double outx = (e[i] & 1) ? (double)(2 * j - 1) * out3[i] : out3[i]; + assert(math::is_approx_rel(vec[j], outx, 1e-12), "pow(%f,%f)=%f is not equal to %f", base3[i], e[i], vec[j], outx); + } + } + +} + +fn void test_sign() @test +{ + int x = -21; + assert(math::sign(x) == -1); + x = 238219382; + assert(math::sign(x) == 1); + x = 0; + assert(math::sign(x) == 0); + uint y = 23; + assert(math::sign(y) == 1); + y = 0; + assert(math::sign(y) == 0); + y = (uint)-21; + assert(math::sign(y) == 1); + assert(@typeis(math::sign(y), uint)); +} + + +fn void test_sin() @test +{ + int [<5>] in = { 231, 1, 0, -1, -231 }; + double [<5>] out = { -0.99568418975810324, 0.84147098480789651 , 0., -0.84147098480789651, 0.99568418975810324 }; + float [<2>] in2 = { math::PI_2, -math::PI_2 }; + float [<2>] out2 = { 1.f, -1.f }; + double [<2>] in3 = { math::PI_2, -math::PI_2 }; + double [<2>] out3 = { 1., -1. }; + assert(@typeis(math::sin(in[0]), double)); + assert(@typeis(math::sin((float)in[0]), float)); + assert(@typeis(math::sin((double)in[0]), double)); + for (int i = 0; i < 5; i++) + { + double x = math::sin(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "sin(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::sin((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "sin(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::sin((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "sin(%f)=%f is not equal to %f", in[i], x, out[i]); + } + float [<2>] vecf = math::sin(in2); + double [<2>] vec = math::sin(in3); + for (int i = 0; i < 2; i++) + { + assert(math::is_approx_rel(vecf[i], out2[i], 1e-6), "sin(%f)=%f is not equal to %f", in2[i], vecf[i], out2[i]); + assert(math::is_approx_rel(vec[i], out3[i], 1e-12), "sin(%f)=%f is not equal to %f", in3[i], vec[i], out3[i]); + } +} + +fn void test_tan() @test +{ + int [<5>] in = { 231, 1, 0, -1, -231 }; + double [<5>] out = { -10.7286365246191129, 1.5574077246549022 , 0., -1.5574077246549022, 10.7286365246191129 }; + float [<2>] in2 = { math::PI_4, -math::PI_4 }; + float [<2>] out2 = { 1.f, -1.f }; + double [<2>] in3 = { math::PI_4, -math::PI_4 }; + double [<2>] out3 = { 1., -1. }; + assert(@typeis(math::tan(in[0]), double)); + assert(@typeis(math::tan((float)in[0]), float)); + assert(@typeis(math::tan((double)in[0]), double)); + for (int i = 0; i < 5; i++) + { + double x = math::tan(in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "tan(%d)=%f is not equal to %f", in[i], x, out[i]); + float f = math::tan((float)in[i]); + assert(math::is_approx_rel(f, (float)out[i], 1e-6), "tan(%f)=%f is not equal to %f", in[i], f, out[i]); + x = math::tan((double)in[i]); + assert(math::is_approx_rel(x, out[i], 1e-12), "tan(%f)=%f is not equal to %f", in[i], x, out[i]); + } + float [<2>] vecf = math::tan(in2); + double [<2>] vec = math::tan(in3); + for (int i = 0; i < 2; i++) + { + assert(math::is_approx_rel(vecf[i], out2[i], 1e-6), "tan(%f)=%f is not equal to %f", in2[i], vecf[i], out2[i]); + assert(math::is_approx_rel(vec[i], out3[i], 1e-12), "tan(%f)=%f is not equal to %f", in3[i], vec[i], out3[i]); + } +} + +fn void test_trunc() @test +{ + double d = -123.9; + assert(math::trunc(d) == -123.0); + d = 123.1; + assert(math::trunc(d) == 123.0); + d = 0.9; + assert(math::trunc(d) == 0); + d = -0.9; + assert(math::trunc(d) == 0); + $assert @typeis(math::trunc(d), double); + float f = -123.9f; + assert(math::trunc(f) == -123.0f); + f = 123.9f; + assert(math::trunc(f) == 123.0f); + f = 0.9f; + assert(math::trunc(f) == 0.0f); + f = -0.9f; + assert(math::trunc(f) == -0.0f); + $assert @typeis(math::trunc(f), float); + double[<5>] vec = { -123.9, 123.9, 0.9, -0.9, 0 }; + assert(math::trunc(vec) == (double[<5>]) { -123, 123, 0, 0, 0 }); +} + +fn void test_round_decimals() @test +{ + double d = 0.532451241142; + float d_f = 0.532451241142; + assert(math::round_to_decimals(d, 2) == 0.53); + assert(math::round_to_decimals(d, 5) == 0.53245); + + assert(math::round_to_decimals(d_f, 2) == 0.53f); + assert(math::round_to_decimals(d_f, 5) == 0.53245f); +} + +fn void test() @test +{ + double radians = math::deg_to_rad(45); + float radians_f = (float)math::deg_to_rad(45); + + assert(math::round_to_decimals(radians, 3) == 0.785); + assert(math::round_to_decimals(radians_f, 3) == 0.785f); +} + +fn void test_muldiv() +{ + char a = 20; + assert(a.muldiv(20, 10) == 40); + ichar b = 20; + assert(b.muldiv(20, -10) == -40); + short c = 16000; + assert(c.muldiv(4, 2) == 32000); + ushort d = 16000; + assert(d.muldiv(8, 2) == 64000); + int e = 1_000_000; + assert(e.muldiv(40000, 10000) == 4_000_000); + uint f = 3_000_000_000u; + assert(f.muldiv(110, 100) == 3_300_000_000u); + long g = 1_000_000_000_000i64; + assert(g.muldiv(2_000_000_000_000i64, 1_000_000_000i64) == 2_000_000_000_000_000i64); + ulong h = 1_000_000_000_000u64; + assert(h.muldiv(2_000_000_000_000u64, 1_000_000_000u64) == 2_000_000_000_000_000u64); + + char[<4>] i = {20, 30, 40, 50}; + assert(i.muldiv(12,10) == (char[<4>]) {24, 36, 48, 60}); + assert(i.muldiv((char[<4>]){11, 12, 13, 14}, (char[<4>]){10,10,10,10}) == (char[<4>]){22, 36, 52, 70}); + + long[<4>] j = {1_000_000_000_000i64, 2_000_000_000_000i64, 3_000_000_000_000i64, 4_000_000_000_000i64}; + assert(j.muldiv(2_000_000_000_000i64, 1_000_000_000i64) == (long[<4>]){2_000_000_000_000_000i64, 4_000_000_000_000_000i64, 6_000_000_000_000_000i64, 8_000_000_000_000_000i64}); + + ichar[<4>] k = {20, 30, 40, 50}; + assert(k.muldiv(20,-10) == (ichar[<4>]){-40,-60,-80,-100}); +} + +fn void test_gcd() @test +{ + assert(math::gcd(20,15) == 5); + assert(math::gcd(15,20) == 5); + assert(math::gcd(-15,20) == 5); + assert(math::gcd(15,-20) == 5); + assert(math::gcd(-15,-20) == 5); + assert(math::gcd(5,15,20) == 5); + assert(math::gcd(1,2,3) == 1); + assert(math::gcd(2,4,6,8) == 2); +} + +fn void test_lcm() @test +{ + assert(math::lcm(4,5) == 20); + assert(math::lcm(6,10) == 30); + assert(math::lcm(-8,20) == 40); + assert(math::lcm(8,-20) == 40); + assert(math::lcm(-8,-20) == 40); + assert(math::lcm(11,17) == 11*17); + assert(math::lcm(11,17,227,263) == 11*17*227*263); +} + diff --git a/test/unit7/stdlib/math/math_complex.c3 b/test/unit7/stdlib/math/math_complex.c3 new file mode 100644 index 000000000..0ad88bffa --- /dev/null +++ b/test/unit7/stdlib/math/math_complex.c3 @@ -0,0 +1,59 @@ +module math_tests @test; +import math_tests::complex; + +def ComplexDouble = ComplexType{double} @local; +def ComplexInt = ComplexType{int} @local; + +module math_tests::complex{ElementType} @test; +import std::math; + +def ComplexType = Complex{ElementType}; + +fn void complex_mul_imaginary() +{ + ComplexType i = complex::IMAGINARY{ElementType}; + assert(i.mul(i).equals((ComplexType){-1, 0})); + assert(i.mul(i).mul(i).equals((ComplexType){0, -1})); +} + +fn void complex_add() +{ + ComplexType a = {3, 4}; + ComplexType b = {1, 2}; + assert(a.add(b).equals((ComplexType){4, 6})); + assert(a.add_each(1).equals((ComplexType){4, 5})); +} + +fn void complex_sub() +{ + ComplexType a = {3, 4}; + ComplexType b = {1, 2}; + assert(a.sub(b).equals((ComplexType){2, 2})); + assert(a.sub_each(1).equals((ComplexType){2, 3})); +} + +fn void complex_scale() +{ + ComplexType a = {2, 1}; + assert(a.scale(2).equals((ComplexType){4, 2})); +} + +fn void complex_conjugate() +{ + ComplexType a = {3, 4}; + assert(a.conjugate().equals((ComplexType){3, -4})); +} + +fn void complex_inverse() @if(types::is_float(ElementType)) +{ + ComplexType a = {3, 4}; + assert(a.inverse().mul(a).equals(complex::IDENTITY{ElementType})); +} + +fn void complex_div() @if(types::is_float(ElementType)) +{ + ComplexType a = {2, 5}; + ComplexType b = {4, -1}; + assert(a.div(b).equals((ComplexType){3.0/17.0, 22.0/17.0})); +} + diff --git a/test/unit7/stdlib/math/math_is_even_odd.c3 b/test/unit7/stdlib/math/math_is_even_odd.c3 new file mode 100644 index 000000000..7acef285c --- /dev/null +++ b/test/unit7/stdlib/math/math_is_even_odd.c3 @@ -0,0 +1,23 @@ +module math_is_even_odd_tests; +import std::math; +import std::io; + +macro test(start, ...) +{ + $for (var $i = 0; $i < $vacount; $i++) + for ($vatype[$i] i = ($vatype[$i])start; i < 5; i+=2) + { + assert(math::is_even(i)); + assert(i.is_even()); + + assert(!math::is_odd(i)); + assert(!i.is_odd()); + } + $endfor; +} + +fn void tests() @test +{ + test(0, char, ushort, uint, ulong, uptr, usz, uint128); + test(-4, ichar, short, int, long, iptr, isz, int128); +} diff --git a/test/unit7/stdlib/math/math_vector.c3 b/test/unit7/stdlib/math/math_vector.c3 new file mode 100644 index 000000000..08be5ea49 --- /dev/null +++ b/test/unit7/stdlib/math/math_vector.c3 @@ -0,0 +1,125 @@ +module std::math::vector @test; +import std::math; + +const EPSILON_F = 1e-6f; +const EPSILON_D = 1e-12; + +fn void test_vec2_init() @test +{ + Vec2f v1 = { 1.0f, 2.0f }; + Vec2 v2 = { 1.0, 2.0 }; + $assert @typeis(v1[0], float); + $assert @typeis(v2[0], double); + assert(v1[0] == 1.0f && v1[1] == 2.0f); + assert(v2[0] == 1.0 && v2[1] == 2.0); +} + +fn void test_vec2_arithmetic() @test +{ + Vec2f v1 = { 1.0f, 2.0f }; + Vec2f v2 = { 2.0f, 3.0f }; + Vec2f sum = v1 + v2; + assert(math::is_approx_rel(sum[0], 3.0f, EPSILON_F)); + assert(math::is_approx_rel(sum[1], 5.0f, EPSILON_F)); + Vec2f diff = v2 - v1; + assert(math::is_approx_rel(diff[0], 1.0f, EPSILON_F)); + assert(math::is_approx_rel(diff[1], 1.0f, EPSILON_F)); + Vec2f scaled = v1 * 2.0f; + assert(math::is_approx_rel(scaled[0], 2.0f, EPSILON_F)); + assert(math::is_approx_rel(scaled[1], 4.0f, EPSILON_F)); +} + +fn void test_vec2_length() @test +{ + Vec2f v = { 3.0f, 4.0f }; + assert(math::is_approx_rel(v.length_sq(), 25.0f, EPSILON_F)); + Vec2f v2 = { 0.0f, 0.0f }; + assert(math::is_approx_rel(v.distance_sq(v2), 25.0f, EPSILON_F)); +} + +fn void test_vec3_operations() @test +{ + Vec3f v1 = { 1.0f, 0.0f, 0.0f }; + Vec3f v2 = { 0.0f, 1.0f, 0.0f }; + Vec3f cross = v1.cross(v2); + assert(math::is_approx_rel(cross[0], 0.0f, EPSILON_F)); + assert(math::is_approx_rel(cross[1], 0.0f, EPSILON_F)); + assert(math::is_approx_rel(cross[2], 1.0f, EPSILON_F)); + float dot = v1.dot(v2); + assert(math::is_approx_rel(dot, 0.0f, EPSILON_F)); +} + +fn void test_vec3_transform() @test +{ + Vec3f v = { 1.0f, 0.0f, 0.0f }; + Vec3f axis = { 0.0f, 0.0f, 1.0f }; + Vec3f rotated = v.rotate_axis(axis, math::PI_2); + assert(math::is_approx_rel(rotated[0] * rotated[0] + rotated[1] * rotated[1], 1.0f, EPSILON_F)); + assert(math::is_approx_rel(rotated[2], 0.0f, EPSILON_F)); + Vec3f perp = v.perpendicular(); + assert(math::is_approx_rel(v.dot(perp), 0.0f, EPSILON_F)); + assert(math::is_approx_rel(perp.length_sq(), v.length_sq(), EPSILON_F)); +} + +fn void test_vec_magnitude() @test +{ + Vec2f v2 = { 3.0f, 4.0f }; + Vec3f v3 = { 2.0f, 2.0f, 1.0f }; + Vec2f clamped2 = v2.clamp_mag(1.0f, 3.0f); + assert(math::is_approx_rel(clamped2.length(), 3.0f, EPSILON_F)); + Vec3f clamped3 = v3.clamp_mag(2.0f, 4.0f); + assert(math::is_approx_rel(clamped3.length(), 3.0f, EPSILON_F)); +} + +fn void test_vec_interpolation() @test +{ + Vec2f start = { 0.0f, 0.0f }; + Vec2f target = { 10.0f, 0.0f }; + float max_dist = 2.0f; + Vec2f result = start.towards(target, max_dist); + assert(math::is_approx_rel(result[0], 2.0f, EPSILON_F)); + assert(math::is_approx_rel(result[1], 0.0f, EPSILON_F)); + Vec2f close_start = { 9.0f, 0.0f }; + result = close_start.towards(target, max_dist); + assert(math::is_approx_rel(result[0], target[0], EPSILON_F)); + assert(math::is_approx_rel(result[1], target[1], EPSILON_F)); +} + +fn void test_vec3_advanced() @test +{ + Vec3f v = { 1.0f, 1.0f, 1.0f }; + Vec3f n = { 0.0f, 1.0f, 0.0f }; + float r = 1.5f; + Vec3f refracted = v.refract(n, r); + assert(refracted.length_sq() > 0.0f); + Vec3f a = { 0.0f, 0.0f, 0.0f }; + Vec3f b = { 1.0f, 0.0f, 0.0f }; + Vec3f c = { 0.0f, 1.0f, 0.0f }; + Vec3f p = { 0.25f, 0.25f, 0.0f }; + Vec3f bary = p.barycenter(a, b, c); + assert(math::is_approx_rel(bary[0] + bary[1] + bary[2], 1.0f, EPSILON_F)); +} + +fn void test_edge_cases() @test +{ + Vec2f zero2 = { 0.0f, 0.0f }; + Vec3f zero3 = { 0.0f, 0.0f, 0.0f }; + assert(zero2.length_sq() == 0.0f); + assert(zero3.length_sq() == 0.0f); + Vec3f perp = zero3.perpendicular(); + assert(perp.length_sq() <= 1.0f + EPSILON_F); + Vec2f clamped = zero2.clamp_mag(1.0f, 2.0f); + assert(clamped.length_sq() == 0.0f); +} + +fn void test_type_consistency() @test +{ + Vec2f vf = { 1.0f, 2.0f }; + Vec2 vd = { 1.0, 2.0 }; + $assert @typeis(vf.length_sq(), float); + $assert @typeis(vd.length_sq(), double); + Vec3f v3f = { 1.0f, 2.0f, 3.0f }; + Vec3 v3d = { 1.0, 2.0, 3.0 }; + $assert @typeis(v3f.cross(v3f)[0], float); + $assert @typeis(v3d.cross(v3d)[0], double); +} \ No newline at end of file diff --git a/test/unit7/stdlib/math/matrix.c3 b/test/unit7/stdlib/math/matrix.c3 new file mode 100644 index 000000000..7bc568410 --- /dev/null +++ b/test/unit7/stdlib/math/matrix.c3 @@ -0,0 +1,122 @@ +module math_matrix @test; +import std::math; + +fn void test_mat4() +{ + { + Matrix4 mat = MATRIX4_IDENTITY; + Matrix4 mat2 = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + Matrix4 calc = mat.mul(mat2); + assert(calc.m == mat.m); + + Matrix4 translated = mat.translate({0.0, 0.0, 0.0}); + assert(translated.m == mat.m); + }; + + { + Matrix4 mat = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; + Matrix4 mat2 = { 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1 }; + Matrix4 calc = mat.mul(mat2); + Matrix4 value = { 56, 46, 36, 26, 152, 126, 100, 74, 56, 46, 36, 26, 152, 126, 100, 74 }; + assert(calc.m == value.m); + }; + + { + Matrix4 result = { + 0.988936, 0.000000, -0.148340, -0.988936, + -0.014599, 0.995146, -0.097325, -2.970838, + 0.147620, 0.098414, 0.984136, -20.765262, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4f result_f = { + 0.988936, 0.000000, -0.148340, -0.988936, + -0.014599, 0.995146, -0.097325, -2.970838, + 0.147620, 0.098414, 0.984136, -20.765262, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4 result_transposed = { + 0.988936, -0.014599, 0.147620, 0.000000, + 0.000000, 0.995146, 0.098414, 0.000000, + -0.148340, -0.097325, 0.984136, 0.000000, + -0.988936, -2.970838, -20.765262, 1.000000 + }; + + Matrix4f result_transposed_f = { + 0.988936, -0.014599, 0.147620, 0.000000, + 0.000000, 0.995146, 0.098414, 0.000000, + -0.148340, -0.097325, 0.984136, 0.000000, + -0.988936, -2.970838, -20.765262, 1.000000 + }; + + Matrix4 look_at = matrix::look_at{double}({4.0, 5.0, 20.0}, {1.0, 3.0, 0.0}, {0.0, 1.0, 0.0}); + Matrix4f look_at_f = matrix::look_at{float}({4.0, 5.0, 20.0}, {1.0, 3.0, 0.0}, {0.0, 1.0, 0.0}); + + assert(math::round_to_decimals((double[<16>])look_at.m, 4) == math::round_to_decimals((double[<16>])result.m, 4)); + assert(math::round_to_decimals((float[<16>])look_at_f.m, 4) == math::round_to_decimals((float[<16>])result_f.m, 4)); + + assert(math::round_to_decimals((double[<16>])result_transposed.m, 4) == math::round_to_decimals((double[<16>])look_at.transpose().m, 4)); + assert(math::round_to_decimals((float[<16>])result_transposed_f.m, 4) == math::round_to_decimals((float[<16>])look_at_f.transpose().m, 4)); + }; + + { + Matrix4 result = { + 1.857087, 0.000000, 0.000000, + 0.000000, 0.000000, 2.414214, + 0.000000, 0.000000, 0.000000, 0.000000, + -1.000200, -0.200020, 0.000000, 0.000000, + -1.000000, 0.000000 + }; + + Matrix4f result_f = { + 1.857087, 0.000000, 0.000000, + 0.000000, 0.000000, 2.414214, + 0.000000, 0.000000, 0.000000, 0.000000, + -1.000200, -0.200020, 0.000000, 0.000000, + -1.000000, 0.000000 + }; + + Matrix4 perspective = matrix4_perspective(math::deg_to_rad(45), 1.3, 0.1, 1000); + Matrix4f perspective_f = matrix4f_perspective((float)math::deg_to_rad(45), 1.3, 0.1, 1000); + + assert(math::round_to_decimals((double[<16>])result.m, 4) == math::round_to_decimals((double[<16>])perspective.m, 4)); + assert(math::round_to_decimals((float[<16>])result_f.m, 4) == math::round_to_decimals((float[<16>])perspective_f.m, 4)); + }; +} + + +fn void test_mat3() +{ + Matrix3 mat = { 3, 5, 3, 5, 2, 6, 6, 2, 1 }; + Matrix3 mat2 = { 4, 2, 6, 7, 8, 9, 2, 3, 4 }; + Matrix3 calc = mat.mul(mat2); + Matrix3 value = { 53, 55, 75, 46, 44, 72, 40, 31, 58 }; + + assert(calc.m == value.m); +} + +fn void test_mat2() +{ + Matrix2 mat = { 3, 5, 5, 2}; + Matrix2 mat2 = { 4, 2, 7, 8 }; + Matrix2 calc = mat.mul(mat2); + Matrix2 value = { 47, 46, 34, 26 }; + + assert(calc.m == value.m); +} + +fn void test_mat2_inverse() +{ + Matrix2 a = { 3, 5, 6, 2 }; + Matrix2 a_inv = a.inverse()!!; + double sum = ((double[<4>])a_inv.mul(a).m).sum(); + assert(math::abs(sum - 2.0) < math::FLOAT_EPSILON, + "wrong inverse: sum of all elements should be 2, but got: %g", sum); +} + +fn void test_vec3() +{ + Vec3 cross = (Vec3){2,3,4}.cross({5,6,7}); + assert(cross == {-3,6,-3}); +} diff --git a/test/unit7/stdlib/math/quaternion.c3 b/test/unit7/stdlib/math/quaternion.c3 new file mode 100644 index 000000000..2438b1de6 --- /dev/null +++ b/test/unit7/stdlib/math/quaternion.c3 @@ -0,0 +1,38 @@ +module math_quaternion @test; +import std::math; + +fn void test() +{ + { + Quaternion rotation = QUATERNION_IDENTITY; + Quaternionf rotation_f = QUATERNIONF_IDENTITY; + assert(rotation.v == {0,0,0,1}); + assert(rotation.v == {0,0,0,1}); + }; + + { + Quaternion rotation = QUATERNION_IDENTITY; + Matrix4 identity_matrix = MATRIX4_IDENTITY; + + Quaternionf rotation_f = QUATERNIONF_IDENTITY; + Matrix4f identity_matrix_f = MATRIX4F_IDENTITY; + + assert((double[<16>])rotation.to_matrix().m == (double[<16>])identity_matrix.m); + assert((float[<16>])rotation_f.to_matrixf().m == (float[<16>])identity_matrix_f.m); + }; + + { + Matrix4 result = { + 0.428571, -0.285714, 0.857143, 0.000000, + 0.857143, 0.428571, -0.285714, 0.000000, + -0.285714, 0.857143, 0.428571, 0.000000, + 0.000000, 0.000000, 0.000000, 1.000000 + }; + + Matrix4 rotation = (Quaternion) {0.5, 0.5, 0.5, 1}.to_matrix(); + Matrix4f rotation_f = (Quaternionf) {0.5, 0.5, 0.5, 1}.to_matrixf(); + + assert(math::round_to_decimals((double[<16>])result.m, 2) == math::round_to_decimals((double[<16>])rotation.m, 2)); + assert(math::round_to_decimals((float[<16>])result.m, 2) == math::round_to_decimals((float[<16>])rotation_f.m, 2)); + }; +} \ No newline at end of file diff --git a/test/unit7/stdlib/math/random.c3 b/test/unit7/stdlib/math/random.c3 new file mode 100644 index 000000000..884ec04e0 --- /dev/null +++ b/test/unit7/stdlib/math/random.c3 @@ -0,0 +1,35 @@ +module std::math::random @test; +import std::math; + +fn void test_regular_random() +{ + for (int i = 0; i < 100; i++) assert(rand(45) < 45 && rand(45) >= 0); +} + +fn void test_next_bool() +{ + DefaultRandom rand; + random::seed_entropy(&rand); + for (int i = 0; i < 100; i++) random::next_bool(&rand); +} + +fn void test_next() +{ + DefaultRandom rand; + random::seed_entropy(&rand); + for (int i = 0; i < 100; i++) assert(random::next(&rand, 100) < 100.0 && random::next(&rand, 100) >= 0); +} + +fn void test_next_double() +{ + DefaultRandom rand; + random::seed_entropy(&rand); + for (int i = 0; i < 100; i++) assert(random::next_double(&rand) < 1.0 && random::next_double(&rand) >= 0); +} + +fn void test_next_float() +{ + DefaultRandom rand; + random::seed_entropy(&rand); + for (int i = 0; i < 100; i++) assert(random::next_float(&rand) < 1.0 && random::next_float(&rand) >= 0); +} \ No newline at end of file diff --git a/test/unit7/stdlib/mem/temp_mem.c3 b/test/unit7/stdlib/mem/temp_mem.c3 new file mode 100644 index 000000000..8d47a8491 --- /dev/null +++ b/test/unit7/stdlib/mem/temp_mem.c3 @@ -0,0 +1,68 @@ +module test; + +fn String add(String s, Allocator a, int x) +{ + if (x < 0) return s.copy(a); + String tmp; + @pool(a) + { + tmp = "foo".tconcat(s); + tmp = add(tmp, a, x - 1); + }; + ulong* y = mem::temp_alloc(ulong); + *y = 0xAAAA_AAAA_AAAA_AAAA; + return tmp.concat(a, "a"); +} + +fn String breakit(String s, Allocator a) +{ + @pool(a) + { + return inner2("foo".concat(tmem(), s), a); + }; +} + +fn String inner2(String s, Allocator a) +{ + @pool(a) + { + ulong* z1 = mem::temp_alloc(ulong); + *z1 = 0xAAAA_AAAA_AAAA_AAAA; + String y = inner3(s, a); + ulong* z = mem::temp_alloc(ulong); + *z = 0xAAAA_AAAA_AAAA_AAAA; + return y; + }; +} + +fn String inner3(String s, Allocator a) +{ + @pool(a) + { + ulong* z1 = mem::temp_alloc(ulong); + *z1 = 0xAAAA_AAAA_AAAA_AAAA; + String y = inner4(s, a); + ulong* z = mem::temp_alloc(ulong); + *z = 0xAAAA_AAAA_AAAA_AAAA; + return y; + }; +} + +fn String inner4(String s, Allocator a) +{ + @pool(a) + { + String y = s.concat(tmem(), "xy**********").copy(a); + return y; + }; +} + +fn void test_temp_allocator() @test +{ + assert("foofoofoofoofoofooabcaaaaaa" == add("abc", allocator::temp(), 5), "was %s", add("abc", allocator::temp(), 5)); +} + +fn void test_temp_allocator2() @test +{ + assert("fooxyz0123456789xy**********" == breakit("xyz0123456789", allocator::temp())); +} diff --git a/test/unit7/stdlib/net/inetaddr.c3 b/test/unit7/stdlib/net/inetaddr.c3 new file mode 100644 index 000000000..6c8572302 --- /dev/null +++ b/test/unit7/stdlib/net/inetaddr.c3 @@ -0,0 +1,49 @@ +module inetaddrtest @test; +import std::net; + +fn void test_ipv4() +{ + InetAddress foo = { .ipv4 = { 127, 0, 0, 0 } }; + assert(foo.ip4.val == 2130706432); + assert(foo.is_loopback()); + +} + +fn void test_ipv4_to_string() +{ + InetAddress a = net::ipv4_from_str("127.0.0.1")!!; + assert(a.to_new_string(allocator::temp()) == "127.0.0.1"); +} + +fn void test_ipv6_to_string() +{ + InetAddress a = net::ipv6_from_str("2001:db8::2:1")!!; + free(a.to_new_string()); + + assert(a.to_new_string(allocator::temp()) == "2001:0db8:0000:0000:0000:0000:0002:0001"); + assert(net::ipv6_from_str("2001:db8::1").to_new_string(allocator::temp())!! == "2001:0db8:0000:0000:0000:0000:0000:0001"); + assert(net::ipv6_from_str("::1").to_new_string(allocator::temp())!! == "0000:0000:0000:0000:0000:0000:0000:0001"); + assert(net::ipv6_from_str("2001::1").to_new_string(allocator::temp())!! == "2001:0000:0000:0000:0000:0000:0000:0001"); + assert(net::ipv6_from_str("2001:db8:1234::").to_new_string(allocator::temp())!! == "2001:0db8:1234:0000:0000:0000:0000:0000"); + assert(net::ipv6_from_str("2001::").to_new_string(allocator::temp())!! == "2001:0000:0000:0000:0000:0000:0000:0000"); + assert(net::ipv6_from_str("::").to_new_string(allocator::temp())!! == "0000:0000:0000:0000:0000:0000:0000:0000"); +} + +fn void test_ipv4_parse() +{ + InetAddress a = net::ipv4_from_str("127.0.0.1")!!; + assert(a.ipv4.a == 127 && a.ipv4.b == 0 && a.ipv4.c == 0 && a.ipv4.d == 1); + a = net::ipv4_from_str("255.254.253.255")!!; + assert(a.ipv4.a == 255 && a.ipv4.b == 254 && a.ipv4.c == 253 && a.ipv4.d == 255); + assert(@catch(net::ipv4_from_str(".1.1.1.1"))); + assert(@catch(net::ipv4_from_str("1..1.1"))); + assert(@catch(net::ipv4_from_str("1..1.1.1"))); + assert(@catch(net::ipv4_from_str("1.1.1.256"))); + assert(@catch(net::ipv4_from_str("256.1.1.1"))); +} + +fn void test_ipv6() +{ + InetAddress foo = { .ipv6 = { 0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888 } }; + assert(foo.ip6.val == 42541956123769884636017138956568135816); +} diff --git a/test/unit7/stdlib/net/url.c3 b/test/unit7/stdlib/net/url.c3 new file mode 100644 index 000000000..ffcd97368 --- /dev/null +++ b/test/unit7/stdlib/net/url.c3 @@ -0,0 +1,459 @@ +module urltest @test; + +import std::io; +import std::net::url; + +// Parser tests + +fn void test_parse_foo() +{ + Url url = url::parse(mem, "foo://example.com:8042/over/there?name=ferret#nose")!!; + defer url.free(); + + assert(url.scheme == "foo", "got '%s'", url.scheme); + assert(url.host == "example.com", "got '%s'", url.host); + assert(url.port == 8042, "got '%d'", url.port); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/over/there", "got '%s'", url.path); + assert(url.query == "name=ferret", "got '%s'", url.query); + assert(url.fragment == "nose", "got: '%s'", url.fragment); +} + +fn void test_parse_urn() +{ + Url url = url::parse(mem, "urn:example:animal:ferret:nose")!!; + defer url.free(); + + assert(url.scheme == "urn"); + assert(url.host == ""); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "example:animal:ferret:nose"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_jdbc() +{ + Url url = url::parse(mem, "jdbc:mysql://test_user:ouupppssss@localhost:3306/sakila?profileSQL=true")!!; + defer url.free(); + + assert(url.scheme == "jdbc:mysql"); + assert(url.host == "localhost"); + assert(url.port == 3306); + assert(url.username == "test_user", "got '%s'", url.username); + assert(url.password == "ouupppssss", "got '%s'", url.password); + assert(url.path == "/sakila"); + assert(url.query == "profileSQL=true"); + assert(url.fragment == ""); +} + +fn void test_parse_ftp() +{ + Url url = url::parse(mem, "ftp://ftp.is.co.za/rfc/rfc1808.txt")!!; + defer url.free(); + + assert(url.scheme == "ftp"); + assert(url.host == "ftp.is.co.za"); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/rfc/rfc1808.txt"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_http() +{ + Url url = url::parse(mem, "http://www.ietf.org/rfc/rfc2396.txt#header1")!!; + defer url.free(); + + assert(url.scheme == "http"); + assert(url.host == "www.ietf.org"); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/rfc/rfc2396.txt"); + assert(url.query == ""); + assert(url.fragment == "header1"); +} + +fn void test_parse_ldap() +{ + Url url = url::parse(mem, "ldap://[2001:db8::7]/c=GB?objectClass=one&objectClass=two")!!; + defer url.free(); + + assert(url.scheme == "ldap"); + assert(url.host == "[2001:db8::7]"); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/c=GB"); + assert(url.query == "objectClass=one&objectClass=two"); + assert(url.fragment == ""); +} + +fn void test_parse_mailto() +{ + Url url = url::parse(mem, "mailto:John.Doe@example.com")!!; + defer url.free(); + + assert(url.scheme == "mailto"); + assert(url.host == ""); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "John.Doe@example.com"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_new_parses() +{ + Url url = url::parse(mem, "news:comp.infosystems.www.servers.unix")!!; + defer url.free(); + + assert(url.scheme == "news"); + assert(url.host == ""); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "comp.infosystems.www.servers.unix"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_tel() +{ + Url url = url::parse(mem, "tel:+1-816-555-1212")!!; + defer url.free(); + + assert(url.scheme == "tel"); + assert(url.host == ""); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "+1-816-555-1212"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_telnet() +{ + Url url = url::parse(mem, "telnet://192.0.2.16:80/")!!; + defer url.free(); + + assert(url.scheme == "telnet"); + assert(url.host == "192.0.2.16"); + assert(url.port == 80); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_urn2() +{ + Url url = url::parse(mem, "urn:oasis:names:specification:docbook:dtd:xml:4.1.2")!!; + defer url.free(); + + assert(url.scheme == "urn"); + assert(url.host == ""); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "oasis:names:specification:docbook:dtd:xml:4.1.2"); + assert(url.query == ""); + assert(url.fragment == ""); +} + +fn void test_parse_empty() +{ + assert(@catch(url::parse(mem, " ")) == UrlParsingResult.EMPTY); +} + +// Parser tests with escape sequences + +fn void test_parse_path_with_escape_sequence() +{ + Url url = url::parse(mem, "foo://example.com:8042/file/name%20one%26two?name=ferret#nose")!!; + defer url.free(); + + assert(url.scheme == "foo", "got '%s'", url.scheme); + assert(url.host == "example.com", "got '%s'", url.host); + assert(url.port == 8042, "got '%d'", url.port); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/file/name one&two", "got '%s'", url.path); + assert(url.query == "name=ferret", "got '%s'", url.query); + assert(url.fragment == "nose", "got: '%s'", url.fragment); +} + +fn void test_parse_username_and_password_with_escape_sequence() +{ + Url url = url::parse(mem, "jdbc:mysql://test%20user:ouu%40pppssss@localhost:3306/sakila?profileSQL=true")!!; + defer url.free(); + + assert(url.scheme == "jdbc:mysql"); + assert(url.host == "localhost"); + assert(url.port == 3306); + assert(url.username == "test user", "got '%s'", url.username); + assert(url.password == "ouu@pppssss", "got '%s'", url.password); + assert(url.path == "/sakila"); + assert(url.query == "profileSQL=true"); + assert(url.fragment == ""); +} + +fn void test_parse_fragment_with_escape_sequence() +{ + Url url = url::parse(mem, "http://www.ietf.org/rfc/rfc2396.txt#header%201%262")!!; + defer url.free(); + + assert(url.scheme == "http"); + assert(url.host == "www.ietf.org"); + assert(url.port == 0); + assert(url.username == "", "got '%s'", url.username); + assert(url.password == "", "got '%s'", url.password); + assert(url.path == "/rfc/rfc2396.txt"); + assert(url.query == ""); + assert(url.fragment == "header 1&2"); +} + +// to_string() tests + +fn void test_string_foo() +{ + Url url = {.scheme="foo", .host="example.com", .port=8042, .path="/over/there", .query="name=ferret", .fragment="nose"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "foo://example.com:8042/over/there?name=ferret#nose"); +} + +fn void test_string_urn() +{ + Url url = {.scheme="urn", .path="example:animal:ferret:nose"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "urn:example:animal:ferret:nose"); +} + +fn void test_string_jdbc() +{ + Url url = {.scheme="jdbc:mysql", .host="localhost", .port=3306, .username="test_user", .password="ouupppssss", .path="/sakila", .query="profileSQL=true"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "jdbc:mysql://test_user:ouupppssss@localhost:3306/sakila?profileSQL=true"); +} + +fn void test_string_ftp() +{ + Url url = {.scheme="ftp", .host="ftp.is.co.za", .path="/rfc/rfc1808.txt"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "ftp://ftp.is.co.za/rfc/rfc1808.txt"); +} + +fn void test_string_http() +{ + Url url = {.scheme="http", .host="www.ietf.org", .path="/rfc/rfc2396.txt", .fragment="header1"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "http://www.ietf.org/rfc/rfc2396.txt#header1", "got: '%s'", str); +} + +fn void test_string_ldap() +{ + Url url = {.scheme="ldap", .host="[2001:db8::7]", .path="/c=GB", .query="objectClass=one&objectClass=two"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "ldap://[2001:db8::7]/c=GB?objectClass=one&objectClass=two", "got: '%s'", str); +} + +fn void test_string_mailto() +{ + Url url = {.scheme="mailto", .path="John.Doe@example.com"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "mailto:John.Doe@example.com"); +} + +fn void test_string_news() +{ + Url url = {.scheme="news", .path="comp.infosystems.www.servers.unix"}; + String str = string::format(mem, "%s", url); + defer free(str); + assert(str == "news:comp.infosystems.www.servers.unix"); +} + +fn void test_string_tel() +{ + Url url = {.scheme="tel", .path="+1-816-555-1212"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "tel:+1-816-555-1212"); +} + +fn void test_string_telnet() +{ + Url url = {.scheme="telnet", .host="192.0.2.16", .port=80, .path="/"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "telnet://192.0.2.16:80/"); +} + +fn void test_string_urn2() +{ + Url url = {.scheme="urn", .path="oasis:names:specification:docbook:dtd:xml:4.1.2"}; + String str = string::format(mem, "%s", url); + defer free(str); + + assert(str == "urn:oasis:names:specification:docbook:dtd:xml:4.1.2"); +} + +fn void test_string_empty() +{ + Url url = {}; + String str = string::format(mem, "%s", url); + defer free(str); + test::eq(str, ""); +} + +// query_values + +fn void test_query_values1() +{ + Url url = url::parse(mem, "foo://example.com:8042/over/there?name=ferret=ok#nose")!!; + defer url.free(); + + UrlQueryValues vals = url::temp_parse_query(url.query); + defer vals.free(); + + assert(vals.len() == 1); + UrlQueryValueList l = vals["name"]!!; + + assert(l.len() == 1); + assert(l[0] == "ferret=ok"); +} + +fn void test_query_values2() +{ + Url url = url::parse(mem, "foo://example.com:8042/over/there?name=ferret&age=99&age=11#nose")!!; + defer url.free(); + + UrlQueryValues vals = url::new_parse_query(url.query); + defer vals.free(); + assert(vals.len() == 2); + + UrlQueryValueList l_name = vals["name"]!!; + assert(l_name.len() == 1); + assert(l_name[0] == "ferret"); + + UrlQueryValueList l_age = vals["age"]!!; + assert(l_age.len() == 2); + assert(l_age[0] == "99"); + assert(l_age[1] == "11"); +} + +fn void test_escaped_query_values() +{ + Url url = url::parse(mem, "foo://example.com:8042/over/there?k%3Bey=%3Ckey%3A+0x90%3E&age=99&age=11#nose")!!; + defer url.free(); + + UrlQueryValues vals = url::new_parse_query(url.query); + defer vals.free(); + assert(vals.len() == 2); + + UrlQueryValueList l_key = vals["k;ey"]!!; + assert(l_key.len() == 1); + assert(l_key[0] == ""); +} + +fn void test_query_values_withempty() +{ + Url url = url::parse(mem, "foo://example.com:8042/over/there?name=ferret&&&age=99&age=11")!!; + defer url.free(); + + UrlQueryValues vals = url::new_parse_query(url.query); + defer vals.free(); + assert(vals.len() == 2); +} + +// url compose and parse should be idempotent + +fn void test_url_idempotence() +{ + UrlQueryValues query_builder; + query_builder.init(mem); + defer query_builder.free(); + + query_builder.add("profileSQL", "true"); + query_builder.add("k;ey", ""); + + String query = string::format(mem, "%s", query_builder); + io::printn(query); + defer free(query); + + Url url = { + .scheme = "jdbc:mysql", + .host = "localhost", + .port = 3306, + .username = "test user", + .password = "ouu@pppssss", + .path = "/sakila", + .query = query, + .fragment = "no se", + }; + + String url_string = string::format(mem, "%s", url); + defer free(url_string); + + String want = "jdbc:mysql://test%20user:ouu%40pppssss@localhost:3306" + "/sakila?profileSQL=true&k%3Bey=%3Ckey%3A+0x90%3E#no%20se"; + assert(url_string == want, "got: %s, want: %s", url_string, want); + + Url parsed = url::parse(mem, url_string)!!; + defer parsed.free(); + + UrlQueryValues vals = url::new_parse_query(parsed.query); + defer vals.free(); + assert(vals.len() == 2); + + UrlQueryValueList key; + key = vals["k;ey"]!!; + assert(key.len() == 1); + assert(key[0] == ""); + + key = vals["profileSQL"]!!; + assert(key.len() == 1); + assert(key[0] == "true"); + + String parsed_query = string::format(mem, "%s", vals); + defer free(parsed_query); + + assert(parsed.scheme == url.scheme); + assert(parsed.host == url.host); + assert(parsed.port == url.port); + assert(parsed.username == url.username); + assert(parsed.password == url.password); + assert(parsed.path == url.path); + assert(parsed.query == parsed_query); + assert(parsed.fragment == url.fragment); + + String parsed_string = string::format(mem, "%s", parsed); + defer free(parsed_string); + + assert(url_string == parsed_string); +} + diff --git a/test/unit7/stdlib/net/url_encoding.c3 b/test/unit7/stdlib/net/url_encoding.c3 new file mode 100644 index 000000000..782c82383 --- /dev/null +++ b/test/unit7/stdlib/net/url_encoding.c3 @@ -0,0 +1,141 @@ +module url_encode_test @test; + +import std::io; +import std::net::url @public; + +struct EncodeTest +{ + String in; + String out; + anyfault err; + UrlEncodingMode mode; +} + +EncodeTest[?] decode_with_error_tests @local = { + { "", "", {}, QUERY, }, + { "abc", "abc", {}, QUERY, }, + { "1%41", "1A", {}, QUERY, }, + { "1%41%42%43", "1ABC", {}, QUERY, }, + { "%4a", "J", {}, QUERY, }, + { "%6F", "o", {}, QUERY, }, + { "%", "", UrlDecodingError.INVALID_HEX, QUERY, }, + { "%a", "", UrlDecodingError.INVALID_HEX, QUERY, }, + { "%1", "", UrlDecodingError.INVALID_HEX, QUERY, }, + { "123%45%6", "", UrlDecodingError.INVALID_HEX, QUERY, }, + { "%zzzzz", "", UrlDecodingError.INVALID_HEX, QUERY, }, + { "a+b", "a b", {}, QUERY, }, + { "a%20b", "a b", {}, QUERY, }, +}; + +fn void test_decoding_with_error() => @pool() +{ + foreach (test: decode_with_error_tests) + { + String! actual = url::tdecode(test.in, test.mode); + if (catch excuse = actual) + { + assert(excuse == test.err, "unescape(%s, %s); " + "got: %s, want: %s", test.in, test.mode, excuse, test.err); + continue; + } + assert(actual == test.out, "unescape(%s, %s); " + "got: %s, want: %s", test.in, test.mode, actual, test.out); + } +} + +EncodeTest[?] encode_tests @local = { + { "", "", {}, PATH, }, + { "abc", "abc", {}, PATH, }, + { "abc+def", "abc+def", {}, PATH, }, + { "a/b", "a/b", {}, PATH, }, + { "one two", "one%20two", {}, PATH, }, + { "10%", "10%25", {}, PATH, }, + { "", "", {}, QUERY, }, + { "abc", "abc", {}, QUERY, }, + { "one two", "one+two", {}, QUERY, }, + { "10%", "10%25", {}, QUERY, }, + { " ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", + "+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B", + {}, QUERY, + }, +}; + +fn void test_percent_encode_and_decode() => @pool() +{ + foreach (test: encode_tests) + { + String actual = url::tencode(test.in, test.mode); + assert(actual == test.out, "escape(%s, %s); " + "got: %s, want: %s", test.in, test.mode, actual, test.out); + + actual = url::tdecode(test.out, test.mode)!!; + assert(actual == test.in, "unescape(%s, %s); " + "got: %s, want: %s", test.out, test.mode, actual, test.in); + } +} + +struct ShouldEncodeTest +{ + char in; + UrlEncodingMode mode; + bool escape; +} + +ShouldEncodeTest[?] should_encode_tests = { + {'a', PATH, false}, + {'a', USERPASS, false}, + {'a', QUERY, false}, + {'a', FRAGMENT, false}, + {'a', HOST, false}, + {'z', PATH, false}, + {'A', PATH, false}, + {'Z', PATH, false}, + {'0', PATH, false}, + {'9', PATH, false}, + {'-', PATH, false}, + {'-', USERPASS, false}, + {'-', QUERY, false}, + {'-', FRAGMENT, false}, + {'.', PATH, false}, + {'_', PATH, false}, + {'~', PATH, false}, + + {'/', USERPASS, true}, + {'?', USERPASS, true}, + {'@', USERPASS, true}, + {'$', USERPASS, false}, + {'&', USERPASS, false}, + {'+', USERPASS, false}, + {',', USERPASS, false}, + {';', USERPASS, false}, + {'=', USERPASS, false}, + + {'!', HOST, false}, + {'$', HOST, false}, + {'&', HOST, false}, + {'\'',HOST, false}, + {'(', HOST, false}, + {')', HOST, false}, + {'*', HOST, false}, + {'+', HOST, false}, + {',', HOST, false}, + {';', HOST, false}, + {'=', HOST, false}, + {'0', HOST, false}, + {'9', HOST, false}, + {'A', HOST, false}, + {'z', HOST, false}, + {'_', HOST, false}, + {'-', HOST, false}, + {'.', HOST, false}, +}; + +fn void test_should_encode() +{ + foreach (test : should_encode_tests) + { + bool actual = url::should_encode(test.in, test.mode); + assert(actual == test.escape, "should_encode(%c, %s); " + "got: %s, want: %s", test.in, test.mode, actual, test.escape); + } +} diff --git a/test/unit7/stdlib/os/env.c3 b/test/unit7/stdlib/os/env.c3 new file mode 100644 index 000000000..363ae423f --- /dev/null +++ b/test/unit7/stdlib/os/env.c3 @@ -0,0 +1,17 @@ +module std::os::env @test; + + fn void set_get_unset() + { + const NAME = "C3_TEST_ENVVAR"; + const VALUE = "foobar"; + + env::set_var(NAME, VALUE); + String v = env::get_var(mem, NAME)!!; + assert(v == VALUE, "got %s; want %s", v, VALUE); + free(v); + env::clear_var(NAME); + if (try env::get_var(mem, NAME)) + { + unreachable("environment variable should no longer exist"); + } + } \ No newline at end of file diff --git a/test/unit7/stdlib/sort/binarysearch.c3 b/test/unit7/stdlib/sort/binarysearch.c3 new file mode 100644 index 000000000..35150222a --- /dev/null +++ b/test/unit7/stdlib/sort/binarysearch.c3 @@ -0,0 +1,37 @@ +module sort_test @test; +import std::sort; + +struct BinarySearchTest +{ + int[] data; + int x; + int index; +} + +fn void binarysearch() +{ + BinarySearchTest[] tcases = { + { {}, 0, 0 }, + { {1, 2, 3}, 1, 0 }, + { {1, 2, 3}, 2, 1 }, + { {1, 2, 3}, 3, 2 }, + { {1, 2, 3}, 4, 3 }, + { {10, 20, 30}, 14, 1 }, + { {10, 20, 30}, 26, 2 }, + }; + + foreach (tc : tcases) + { + usz idx = sort::binarysearch(tc.data, tc.x); + assert(idx == tc.index, "%s: got %d; want %d", tc.data, idx, tc.index); + + usz cmp_idx = sort::binarysearch(tc.data, tc.x, &sort::cmp_int_ref); + assert(cmp_idx == tc.index, "%s: got %d; want %d", tc.data, cmp_idx, tc.index); + + usz cmp_idx2 = sort::binarysearch(tc.data, tc.x, &sort::cmp_int_value); + assert(cmp_idx2 == tc.index, "%s: got %d; want %d", tc.data, cmp_idx2, tc.index); + + usz cmp_idx3 = sort::binarysearch(tc.data, tc.x, fn int(int a, int b) => a - b); + assert(cmp_idx3 == tc.index, "%s: got %d; want %d", tc.data, cmp_idx2, tc.index); + } +} \ No newline at end of file diff --git a/test/unit7/stdlib/sort/countingsort.c3 b/test/unit7/stdlib/sort/countingsort.c3 new file mode 100644 index 000000000..8c1ae88cb --- /dev/null +++ b/test/unit7/stdlib/sort/countingsort.c3 @@ -0,0 +1,100 @@ +module sort_test @test; +import std::math; +import std::sort; +import sort::check; +import std::collections::list; + +fn void countingsort() +{ + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc); + assert(check::int_ascending_sort(tc)); + } +} + +fn void countingsort_with_ref() +{ + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, &sort::key_int_ref); + assert(check::int_ascending_sort(tc)); + } +} + +fn void countingsort_with_value() +{ + int[][] tcases = { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, &sort::key_int_value); + assert(check::int_ascending_sort(tc)); + } +} + +fn void countingsort_with_lambda() +{ + int[][] tcases = + { + {}, + {[0..128] = 10, [129..192] = 3}, + {[0..128] = 3, [129..192] = 2, [193..200] = 1}, + {[0..128] = 1, [129..192] = 2, [193..200] = 3}, + {[0..128] = 2, [129..192] = 1, [193..200] = 3}, + }; + + foreach (tc : tcases) + { + sort::countingsort(tc, fn uint(int a) => ((uint)(a + int.min))); + assert(check::int_ascending_sort(tc)); + } +} + +def CountingSortTestList = List{int}; + +fn void countingsort_list() +{ + CountingSortTestList list; + list.add_array({ 2, 1, 3}); + sort::countingsort(list, &sort::key_int_value); + assert(check::int_ascending_sort(list.array_view())); +} + +fn void countingsort_random_large_list() +{ + Lcg128Random random; + random::seed_entropy(&random); + + CountingSortTestList list; + for (usz i = 0; i < 2048; i++) + { + list.push(random.next_int()); + } + + sort::countingsort(list, &sort::key_int_value); + assert(check::int_ascending_sort(list.array_view())); + list.free(); +} \ No newline at end of file diff --git a/test/unit7/stdlib/sort/insertionsort.c3 b/test/unit7/stdlib/sort/insertionsort.c3 new file mode 100644 index 000000000..57dfe9d0d --- /dev/null +++ b/test/unit7/stdlib/sort/insertionsort.c3 @@ -0,0 +1,102 @@ +module sort_test @test; +import std::sort; +import sort::check; +import std::collections::list; + +fn void insertionsort() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::insertionsort(tc); + assert(check::int_ascending_sort(tc)); + } +} + +fn void insertionsort_with_ref() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::insertionsort(tc, &sort::cmp_int_ref); + assert(check::int_ascending_sort(tc)); + } +} + +fn void insertionsort_with_value() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::insertionsort(tc, &sort::cmp_int_value); + assert(check::int_ascending_sort(tc)); + } +} + +fn void insertionsort_with_array() +{ + int[?] a = { 4, 8, 100, 1, 2 }; + sort::insertionsort(&a); + assert(a == { 1, 2, 4, 8, 100 }); +} + +fn void insertionsort_with_lambda() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::insertionsort(tc, fn int(int a, int b) => a - b); + assert(check::int_ascending_sort(tc)); + } +} + +def InsertionSortTestList = List{int}; + +fn void insertionsort_list() +{ + InsertionSortTestList list; + list.add_array({ 2, 1, 3}); + sort::insertionsort(list, &sort::cmp_int_value); + assert(check::int_ascending_sort(list.array_view())); +} + +module sort::check; + +fn bool int_ascending_sort(int[] list) +{ + int prev = int.min; + foreach (x : list) + { + if (prev > x) return false; + prev = x; + } + return true; +} diff --git a/test/unit7/stdlib/sort/quickselect.c3 b/test/unit7/stdlib/sort/quickselect.c3 new file mode 100644 index 000000000..e351cd297 --- /dev/null +++ b/test/unit7/stdlib/sort/quickselect.c3 @@ -0,0 +1,63 @@ +module sort_test @test; +import std::sort; + +struct TestCase @local +{ + int[] list; + isz k; + int want; +} + +fn void quickselect() +{ + TestCase[] tcases = { + { + .list = {3, 4, 1}, + .k = 0, + .want = 1, + }, + { + .list = {3, 4, 1}, + .k = 1, + .want = 3, + }, + { + .list = {3, 4, 1}, + .k = 2, + .want = 4, + }, + { + .list = {3, 2, 4, 1}, + .k = 1, + .want = 2, + }, + { + .list = {3, 2, 1, 2}, + .k = 1, + .want = 2, + }, + { + .list = {3, 2, 1, 2}, + .k = 2, + .want = 2, + }, + { + .list = {3, 2, 1, 2}, + .k = 3, + .want = 3, + }, + }; + + foreach (i, tc : tcases) + { + if (try got = sort::quickselect(tc.list, tc.k)) + { + assert(got == tc.want, "got: %d, want %d", got, tc.want); + } + else + { + assert(false, "test %d failed", i); + } + } +} + diff --git a/test/unit7/stdlib/sort/quicksort.c3 b/test/unit7/stdlib/sort/quicksort.c3 new file mode 100644 index 000000000..8cecec30e --- /dev/null +++ b/test/unit7/stdlib/sort/quicksort.c3 @@ -0,0 +1,102 @@ +module sort_test @test; +import std::sort; +import sort::check; +import std::collections::list; + +fn void quicksort() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort(tc); + assert(check::int_sort(tc)); + } +} + +fn void quicksort_with_ref() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort(tc, &sort::cmp_int_ref); + assert(check::int_sort(tc)); + } +} + +fn void quicksort_with_array() +{ + int[?] a = { 4, 8, 100, 1, 2 }; + sort::quicksort(&a); + assert(a == { 1, 2, 4, 8, 100 }); +} + +fn void quicksort_with_value() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort(tc, &sort::cmp_int_value); + assert(check::int_sort(tc)); + } +} + +fn void quicksort_with_lambda() +{ + int[][] tcases = { + {}, + {10, 3}, + {3, 2, 1}, + {1, 2, 3}, + {2, 1, 3}, + }; + + foreach (tc : tcases) + { + sort::quicksort(tc, fn int(int a, int b) => a - b); + assert(check::int_sort(tc)); + } +} + +def List = List{int}; + +fn void quicksort_list() +{ + List list; + list.add_array({ 2, 1, 3}); + sort::quicksort(list, &sort::cmp_int_value); + assert(check::int_sort(list.array_view())); +} + +module sort::check; + +fn bool int_sort(int[] list) +{ + int prev = int.min; + foreach (x : list) + { + if (prev > x) return false; + prev = x; + } + return true; +} \ No newline at end of file diff --git a/test/unit7/stdlib/sort/sort.c3 b/test/unit7/stdlib/sort/sort.c3 new file mode 100644 index 000000000..df7052cff --- /dev/null +++ b/test/unit7/stdlib/sort/sort.c3 @@ -0,0 +1,22 @@ +module std::sort; + +fn int cmp_int_ref(int* x, int* y) +{ + return *x - *y; +} + +fn int cmp_int_value(int x, int y) +{ + return x - y; +} + + +fn uint key_int_ref(int* x) +{ + return (uint)(*x + int.min); +} + +fn uint key_int_value(int x) +{ + return (uint)(x + int.min); +} \ No newline at end of file diff --git a/test/unit7/stdlib/sort/sorted.c3 b/test/unit7/stdlib/sort/sorted.c3 new file mode 100644 index 000000000..91950acc6 --- /dev/null +++ b/test/unit7/stdlib/sort/sorted.c3 @@ -0,0 +1,92 @@ +module sort_test @test; +import std::sort; +import std::collections::list; + +struct TestCase @local +{ + int[] input; + bool want; +} + +fn void sorted() +{ + TestCase[] tcases = { + { + .input = {}, + .want = true, + }, + { + .input = {1}, + .want = true, + }, + { + .input = {1,2}, + .want = true, + }, + { + .input = {2,1}, + .want = true, + }, + { + .input = {1,2,3}, + .want = true, + }, + { + .input = {1,2,1}, + .want = false, + }, + { + .input = {2,1,2}, + .want = false, + }, + { + .input = {3,2,1}, + .want = true, + }, + { + .input = {1,1,1,1,1,2}, + .want = true, + }, + { + .input = {2,2,2,2,2,1}, + .want = true, + }, + { + .input = {1,1,1,1,2,1}, + .want = false, + }, + }; + + bool got; + foreach (tc : tcases) + { + // default + got = is_sorted(tc.input); + assert(got == tc.want, "default: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with list + List{int} list; + list.tinit(); + list.add_array(tc.input); + + got = is_sorted(list); + assert(got == tc.want, "list: %s, got: %s, want: %s", + list, got, tc.want); + + // with lambda + got = is_sorted(tc.input, fn int(int a, int b) => a - b); + assert(got == tc.want, "lambda: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with value + got = is_sorted(tc.input, &sort::cmp_int_value); + assert(got == tc.want, "value: %s, got: %s, want: %s", + tc.input, got, tc.want); + + // with ref + got = is_sorted(tc.input, &sort::cmp_int_ref); + assert(got == tc.want, "ref: %s, got: %s, want: %s", + tc.input, got, tc.want); + } +} diff --git a/test/unit7/stdlib/string.c3 b/test/unit7/stdlib/string.c3 new file mode 100644 index 000000000..058059c8d --- /dev/null +++ b/test/unit7/stdlib/string.c3 @@ -0,0 +1,20 @@ +module string_test; + +fn void test_clear() @test +{ + DString s = dstring::new_with_capacity(mem, 32); + defer s.free(); + assert(s.len() == 0); + assert(s.capacity() == 32); + s.append_repeat('x', 63); + assert(s.capacity() == 64); + assert(s.len() == 63); + char* addr = (char*)s.str_view(); + s.clear(); + assert(s.capacity() == 64); + assert(s.len() == 0); + s.append_repeat('x', 63); + assert(s.capacity() == 64); + assert(s.len() == 63); + assert(addr == (char*)s.str_view()); +} \ No newline at end of file diff --git a/test/unit7/stdlib/string_to_float.c3 b/test/unit7/stdlib/string_to_float.c3 new file mode 100644 index 000000000..c2cd94925 --- /dev/null +++ b/test/unit7/stdlib/string_to_float.c3 @@ -0,0 +1,29 @@ +module string_to_float_tests; + +fn void test_float() @test +{ + assert(String.to_float("1.2")!! == 1.2f); + assert(String.to_float("10")!! == 10f); + assert(String.to_float(".7647834")!! == 0.7647834f); + assert(String.to_float("0.213232")!! == 0.213232f); + assert(String.to_float("000001.487348")!! == 000001.487348f); + assert(String.to_float("3.54500000")!! == 3.54500000f); + assert(String.to_float("4.0")!! == 4.0f); + assert(String.to_float("-23.545")!! == -23.545f); + assert(String.to_float("1.5555555555555")!! == 1.5555555555555f); + assert(String.to_float("1.5555555555556666")!! == 1.5555555555556666f); +} + +fn void test_double() @test +{ + assert(String.to_double("1.2")!! == 1.2); + assert(String.to_double("10")!! == 10); + assert(String.to_double(".7647834")!! == 0.7647834); + assert(String.to_double("0.213232")!! == 0.213232); + assert(String.to_double("000001.487348")!! == 000001.487348); + assert(String.to_double("3.54500000")!! == 3.54500000); + assert(String.to_double("4.0")!! == 4.0); + assert(String.to_double("-23.545")!! == -23.545); + assert(String.to_double("1.5555555555555")!! == 1.5555555555555); + assert(String.to_double("1.5555555555556666")!! == 1.5555555555556666); +} \ No newline at end of file diff --git a/test/unit7/stdlib/threads/channel.c3 b/test/unit7/stdlib/threads/channel.c3 new file mode 100644 index 000000000..90b55d05a --- /dev/null +++ b/test/unit7/stdlib/threads/channel.c3 @@ -0,0 +1,337 @@ +module thread_test; + +import std::thread::channel; +import std::thread; +import std::time; +import std::io; + +fn void init_destroy_buffered() @test +{ + for (usz i = 0; i < 20; i++) + { + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + } +} + +fn void init_destroy_unbuffered() @test +{ + for (usz i = 0; i < 20; i++) + { + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + } +} + +fn void push_to_buffered_channel_no_lock() @test +{ + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + + c.push(1)!!; +} + +fn void push_pop_buffered_no_locks() @test +{ + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + + c.push(123)!!; + int got = c.pop()!!; + assert(got == 123); +} + +fn void push_pop_unbuffered_with_locks() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + UnbufferedChannel{int} c = (UnbufferedChannel{int})arg; + c.push(123)!!; + c.push(321)!!; + return 0; + }, (void*)c)!!; + + int got = c.pop()!!; + assert(got == 123); + got = c.pop()!!; + assert(got == 321); +} + +fn void sending_to_closed_unbuffered_chan_is_forbidden() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + c.close()!!; + + if (catch err = c.push(123)) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + assert(false); +} + +fn void sending_to_closed_buffered_chan_is_forbidden() @test +{ + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + + c.close()!!; + + if (catch err = c.push(123)) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + assert(false); +} + +fn void reading_from_empty_closed_unbuffered_chan_is_forbidden() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + c.close()!!; + + if (catch err = c.pop()) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + assert(false); +} + +fn void reading_from_empty_closed_buffered_chan_is_forbidden() @test +{ + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + + c.close()!!; + + if (catch err = c.pop()) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + assert(false); +} + +fn void reading_from_non_empty_closed_buffered_chan_is_ok() @test +{ + BufferedChannel{int} c; + c.new_init(3)!!; + defer c.destroy()!!; + + c.push(1)!!; + c.push(2)!!; + c.push(3)!!; + + c.close()!!; + + int got = c.pop()!!; + assert(got == 1); + got = c.pop()!!; + assert(got == 2); + got = c.pop()!!; + assert(got == 3); + + int! got_err = c.pop(); + if (catch err = got_err) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + + assert(false); +} + +fn void reading_from_empty_buffered_chan_aborted_by_close() @test +{ + BufferedChannel{int} c; + c.new_init(3)!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + BufferedChannel{int} c = (BufferedChannel{int})arg; + c.close()!!; + return 0; + }, (void*)c)!!; + + int! res = c.pop(); + if (catch err = res) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + + assert(false); +} + +fn void reading_from_unbuffered_chan_aborted_by_close() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + UnbufferedChannel{int} c = (UnbufferedChannel{int})arg; + c.close()!!; + return 0; + }, (void*)c)!!; + + int! res = c.pop(); + if (catch err = res) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + + assert(false); +} + +fn void sending_to_full_buffered_chan_aborted_by_close() @test +{ + BufferedChannel{int} c; + c.new_init(1)!!; + defer c.destroy()!!; + + c.push(1)!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + BufferedChannel{int} c = (BufferedChannel{int})arg; + c.close()!!; + return 0; + }, (void*)c)!!; + + anyfault err = @catch(c.push(1)); + if (err) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + + assert(false); +} + +fn void sending_to_unbuffered_chan_aborted_by_close() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + UnbufferedChannel{int} c = (UnbufferedChannel{int})arg; + c.close()!!; + return 0; + }, (void*)c)!!; + + anyfault err = @catch(c.push(1)); + if (err) + { + assert(err == ThreadFault.CHANNEL_CLOSED); + return; + } + + assert(false); +} + +fn void multiple_actions_unbuffered() @test +{ + UnbufferedChannel{int} c; + c.new_init()!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + UnbufferedChannel{int} c = (UnbufferedChannel{int})arg; + for (int i = 0; i <= 100; i++) + { + c.push(i)!!; + } + return 0; + }, (void*)c)!!; + + int sum; + + for (int i = 0; i <= 100; i++) + { + int! res = c.pop(); + if (catch err = res) + { + assert(false); + } + sum += res; + } + + assert(sum == 5050); +} + +fn void multiple_actions_buffered() @test +{ + BufferedChannel{int} c; + c.new_init(10)!!; + defer c.destroy()!!; + + Thread thread; + defer thread.join()!!; + + thread.create(fn int(void* arg) + { + BufferedChannel{int} c = (BufferedChannel{int})arg; + for (int i = 0; i <= 100; i++) + { + c.push(i)!!; + } + return 0; + }, (void*)c)!!; + + int sum; + + for (int i = 0; i <= 100; i++) + { + int! res = c.pop(); + if (catch err = res) + { + assert(false); + } + sum += res; + } + + assert(sum == 5050); +} + diff --git a/test/unit7/stdlib/threads/mutex.c3 b/test/unit7/stdlib/threads/mutex.c3 new file mode 100644 index 000000000..8e47b32ea --- /dev/null +++ b/test/unit7/stdlib/threads/mutex.c3 @@ -0,0 +1,147 @@ +module thread_test; + +import std::thread; +import std::os; + +const TEST_MAGNITUDE = 10; + +fn void lock_control_test() @test +{ + Mutex m; + m.init()!!; + m.lock()!!; + assert(@catch(m.lock())); +} + +fn void unlock_control_test() @test +{ + Mutex m; + m.init()!!; + assert(@catch(m.unlock())); +} + +fn void lock_with_double_unlock_test() @test +{ + Mutex m; + m.init()!!; + + m.lock()!!; + m.unlock()!!; + assert(@catch(m.unlock())); +} + +fn void! own_mutex(Mutex* m) +{ + m.lock()!; + m.unlock()!; +} + +fn void ensure_owner_checks() @test +{ + Mutex m; + m.init()!!; + + Thread[3 * TEST_MAGNITUDE] threads; + + foreach(&t : threads) + { + t.create((ThreadFn)&own_mutex, &m)!!; + } + + foreach(&t : threads) + { + t.join()!!; + } + + own_mutex(&m)!!; +} + +struct ArgsWrapper1 +{ + Mutex* m; + ulong* v; +} + +fn void shared_mutex_increment(ArgsWrapper1* args) +{ + args.m.lock()!!; + args.v++; + args.m.unlock()!!; +} + +fn void shared_mutex_decrement(ArgsWrapper1* args) +{ + args.m.lock()!!; + args.v--; + args.m.unlock()!!; +} + +fn void shared_mutex() @test +{ + Mutex m; + m.init()!!; + m.lock()!!; + + ulong v; + + ArgsWrapper1 args = + { + .m = &m, + .v = &v + }; + + // An even number of threads must be chosen + Thread[6 * TEST_MAGNITUDE] threads; + for (int i = 0; i < threads.len / 2; i++) + { + (&threads[i]).create((ThreadFn)&shared_mutex_increment, &args)!!; + } + for (int i = (threads.len / 2); i < threads.len; i++) + { + (&threads[i]).create((ThreadFn)&shared_mutex_decrement, &args)!!; + } + + m.unlock()!!; + foreach(&t : threads) + { + t.join()!!; + } + assert(v == 0); +} + +// Recursive mutex + +fn void acquire_recursively(RecursiveMutex* m) +{ + // TODO: The recursive mutex functions can not directly be called via pointer + + for (usz i = 0; i < 5 * TEST_MAGNITUDE; i++) + { + ((Mutex*)m).lock()!!; + } + + for (usz i = 0; i < 5 * TEST_MAGNITUDE; i++) + { + ((Mutex*)m).unlock()!!; + } +} + +fn void test_recursive_mutex() @test +{ + RecursiveMutex m; + m.init()!!; + defer m.destroy()!!; + + Thread[3 * TEST_MAGNITUDE] threads; + foreach(&t : threads) + { + t.create((ThreadFn)&acquire_recursively, &m)!!; + } + + foreach(&t : threads) + { + t.join()!!; + } + + return acquire_recursively(&m); +} diff --git a/test/unit7/stdlib/threads/pool.c3 b/test/unit7/stdlib/threads/pool.c3 new file mode 100644 index 000000000..28380f7b5 --- /dev/null +++ b/test/unit7/stdlib/threads/pool.c3 @@ -0,0 +1,79 @@ +module thread_pool_test; +import std::io; +import std::thread; +import std::thread::pool; + +def Pool = ThreadPool{4}; + +fn void init_destroy() @test +{ + for (usz i = 0; i < 20; i++) + { + Pool pool; + pool.init()!!; + pool.destroy()!!; + } +} + +fn void push_destroy() @test +{ + for FOO: (usz i = 0; i < 20; i++) + { + x = 0; + int y = 20; + Pool pool; + pool.init()!!; + defer pool.destroy()!!; + work_done.lock()!!; + pool.push(&do_work, &y)!!; + work_done.unlock()!!; + for (int j = 0; j < 1000; j++) + { + work_done.lock()!!; + if (@atomic_load(x) == @atomic_load(y)) + { + (void)work_done.unlock(); + break FOO; + } + (void)work_done.unlock(); + thread::yield(); + } + assert(false, "y never changed"); + } +} + +fn void push_stop() @test +{ + for (usz i = 0; i < 20; i++) + { + x = 0; + int y = 20; + Pool pool; + pool.init()!!; + work_done.lock()!!; + pool.push(&do_work, &y)!!; + work_done.unlock()!!; + pool.stop_and_destroy()!!; + assert(x == y, "%d: %d != %d", i, x, y); + } +} + +int x; + +Mutex work_done; + +fn void startup() @init { + work_done.init()!!; +} + +fn void shutdown() @finalizer { + (void)work_done.destroy(); +} + +fn int do_work(void* arg) +{ + work_done.lock()!!; + x = *(int*)arg; + work_done.unlock()!!; + return 0; +} \ No newline at end of file diff --git a/test/unit7/stdlib/threads/simple_thread.c3 b/test/unit7/stdlib/threads/simple_thread.c3 new file mode 100644 index 000000000..211599209 --- /dev/null +++ b/test/unit7/stdlib/threads/simple_thread.c3 @@ -0,0 +1,90 @@ +import std::thread; +import std::io; + +int a; + +fn void testrun() @test +{ + Thread t; + a = 0; + t.create(fn int(void* arg) { a++; return 0; }, null)!!; + assert(t.join()!! == 0); + assert(a == 1); + + t.create(fn int(void* arg) { return 10; }, null)!!; + assert(t.join()!! == 10); +} + + +Mutex m_global; + +fn void testrun_mutex() +{ + Thread[20] ts; + a = 0; + m_global.init()!!; + foreach (&t : ts) + { + t.create(fn int(void* arg) { + m_global.lock()!!; + defer m_global.unlock()!!; + a += 10; + thread::sleep_ms(5); + a *= 10; + thread::sleep_ms(5); + a /= 10; + thread::sleep_ms(5); + a -= 10; + thread::sleep_ms(5); + a++; + return 0; + }, null)!!; + } + foreach (&t : ts) + { + assert(t.join()!! == 0); + } + assert(a == ts.len); + m_global.destroy()!!; +} + +fn void testrun_mutex_try() @test +{ + Mutex m; + m.init()!!; + m.lock()!!; + assert(m.try_lock() == false); + m.unlock()!!; + assert(m.try_lock() == true); + m.unlock()!!; +} + +fn void testrun_mutex_timeout() @test +{ + TimedMutex m; + m.init()!!; + m.lock()!!; + if (try m.lock_timeout(20)) + { + unreachable("lock_timeout should fail"); + } + m.unlock()!!; + m.lock_timeout(20)!!; + m.unlock()!!; +} + +int x_once = 100; + +fn void call_once() +{ + x_once += 100; +} + +fn void testrun_once() @test +{ + OnceFlag once; + once.call(&call_once); + assert(x_once == 200); + once.call(&call_once); + assert(x_once == 200); +} \ No newline at end of file diff --git a/test/unit7/stdlib/time/datetime.c3 b/test/unit7/stdlib/time/datetime.c3 new file mode 100644 index 000000000..81770a83e --- /dev/null +++ b/test/unit7/stdlib/time/datetime.c3 @@ -0,0 +1,151 @@ +module datetime_test @test; +import std::time; + +fn void test_parse_and_add() +{ +DateTime d = datetime::from_date(1973, APRIL, 27); + assert(d.year == 1973); + assert(d.month == APRIL); + assert(d.day == 27); + assert(d.hour == 0); + assert(d.min == 0); + d = d.add_weeks(1); + assert(d.year == 1973); + assert(d.month == MAY); + assert(d.day == 4); + assert(d.hour == 0); + assert(d.min == 0); + d = d.add_years(2); + assert(d.year == 1975); + assert(d.month == MAY); + assert(d.day == 4); + assert(d.hour == 0); + assert(d.min == 0); + DateTime x = d.add_months(-14); + assert(x.year == 1974); + assert(x.month == MARCH); + assert(x.day == 4); + assert(x.hour == 0); + assert(x.min == 0); + x = d.add_months(-12); + assert(x.year == 1974); + assert(x.month == MAY); + x = d.add_months(12); + assert(x.year == 1976); + assert(x.month == MAY); + x = d.add_months(3); + assert(x.year == 1975); + assert(x.month == AUGUST); + x = d.add_months(15); + assert(x.year == 1976); + assert(x.month == AUGUST); + x = d.add_months(0); + assert(x.year == 1975); + assert(x.month == MAY); +} + +fn void test_timezone() +{ + int offset_hours = 7; + int offset = offset_hours * 3600; + + int target_offset_hours = -4; + int target_offset = target_offset_hours * 3600; + + DateTime d1 = datetime::from_date(2022, OCTOBER, 15); + TzDateTime d2 = datetime::from_date_tz(2022, OCTOBER, 15, gmt_offset: offset); + + DateTime dt; + TzDateTime tz; + + Time t1 = d1.time; + Time t2 = d2.time; + + assert(t1 == 1665792000000000); + assert(t2 == 1665766800000000); + + // to_gmt_offset should keep the timesampt value + tz = d1.to_gmt_offset(offset); + assert(tz.time == t1); + assert(tz.year == 2022); + assert(tz.month == OCTOBER); + assert(tz.day == 15); + assert(tz.hour == offset_hours); + assert(tz.min == 0); + assert(tz.gmt_offset == offset); + + // with_gmt_offset should keep the date/time values and adjust the timestamp + tz = d1.with_gmt_offset(offset); + assert(tz.time == t2); + assert(tz.year == d1.year); + assert(tz.month == d1.month); + assert(tz.day == d1.day); + assert(tz.hour == d1.hour); + assert(tz.min == d1.min); + assert(tz.gmt_offset == offset); + + dt = datetime::from_time(t1); + assert(dt.day == 15); + assert(dt.hour == 0); + dt = datetime::from_time(t2); + assert(dt.day == 14); + assert(dt.hour == 24 - offset_hours); + + // from_time_tz should keep the timesampt value + tz = datetime::from_time_tz(t1, offset); + assert(tz.time == t1); + assert(tz.day == 15); + assert(tz.hour == offset_hours); + assert(tz.gmt_offset == offset); + tz = datetime::from_time_tz(t2, offset); + assert(tz.time == t2); + assert(tz.day == 15); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); + + // The add_* methods should adjust the targeted date/time values while + // keeping the others unchanged. The gmt_offset should be kept as well. + dt = d1.add_hours(1); + assert(dt.day == 15); + assert(dt.hour == 1); + tz = d2.add_hours(1); + assert(tz.day == 15); + assert(tz.hour == 1); + assert(tz.gmt_offset == offset); + + dt = d1.add_days(1); + assert(dt.day == 16); + assert(dt.hour == 0); + tz = d2.add_days(1); + assert(tz.day == 16); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); + + dt = d1.add_months(1); + assert(dt.month == NOVEMBER); + assert(dt.day == 15); + assert(dt.hour == 0); + tz = d2.add_months(1); + assert(tz.month == NOVEMBER); + assert(tz.day == 15); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); + + // Conversion from GMT+7 to GMT-4 + tz = d2.to_gmt_offset(target_offset); + assert(tz.year == d2.year); + assert(tz.month == d2.month); + assert(tz.day == d2.day - 1); + assert(tz.hour == 24 - (offset_hours - target_offset_hours)); + assert(tz.time == t2); + assert(tz.gmt_offset == target_offset); + + // Conversion with the same offset + tz = d2.to_gmt_offset(offset); + assert(tz.year == d2.year); + assert(tz.month == d2.month); + assert(tz.day == d2.day); + assert(tz.hour == d2.hour); + assert(tz.time == t2); + assert(tz.gmt_offset == offset); +} diff --git a/test/unit7/stdlib/time/format.c3 b/test/unit7/stdlib/time/format.c3 new file mode 100644 index 000000000..81f0b0357 --- /dev/null +++ b/test/unit7/stdlib/time/format.c3 @@ -0,0 +1,51 @@ +module timeformat_test @test; + +import std::time::datetime, std::collections::list, std::collections::triple; + +def FormatTzTestSpec = Triple{TzDateTime, DateTimeFormat, String}; +def FormatTestSpec = Triple{DateTime, DateTimeFormat, String}; + +fn void test_with_tz() +{ + FormatTzTestSpec[?] tests = { + { datetime::from_date(1970, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123, "Thu, 01 Jan 1970 00:00:00 GMT" }, + { datetime::from_date(1994, Month.from_ordinal(10), 6, 8, 49, 37).with_gmt_offset(0), RFC1123, "Sun, 06 Nov 1994 08:49:37 GMT" }, + { datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123, "Wed, 01 Jan 2020 00:00:00 GMT" }, + { datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(-3600), RFC1123, "Wed, 01 Jan 2020 01:00:00 GMT" }, + { datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(-3600), RFC1123Z, "Wed, 01 Jan 2020 00:00:00 -0100" }, + { datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123Z, "Wed, 01 Jan 2020 00:00:00 -0000" }, + { datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC3339, "2020-01-01T00:00:00Z" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(0), RFC3339MS, "2006-01-02T15:04:05.999998Z" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(25200), RFC3339Z, "2006-01-02T15:04:05+07:00" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(25200), RFC3339ZMS, "2006-01-02T15:04:05.999998+07:00" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC822, "02 Jan 06 15:04 GMT" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC822Z, "02 Jan 06 15:04 -0000" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC850, "Monday, 02-Jan-06 15:04:05 GMT" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), UNIXDATE, "Mon Jan 2 15:04:05 GMT 2006" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).to_gmt_offset(0), RUBYDATE, "Mon Jan 2 15:04:05 -0000 2006" }, + }; + + foreach (test : tests) + { + String candidate = test.first.format(mem, test.second); + defer free(candidate); + assert(candidate == test.third, "got: '%s', expected: '%s'", candidate, test.third); + } +} + +fn void test_without_tz() +{ + FormatTestSpec[?] tests = { + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), ANSIC, "Mon Jan 2 15:04:05 2006" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), DATETIME, "2006-01-02 15:04:05" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), DATEONLY, "2006-01-02" }, + { datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), TIMEONLY, "15:04:05" }, + }; + + foreach (test : tests) + { + String candidate = test.first.format(mem, test.second); + defer free(candidate); + assert(candidate == test.third, "got: '%s', expected: '%s'", candidate, test.third); + } +} diff --git a/test/unit7/stdlib/time/time.c3 b/test/unit7/stdlib/time/time.c3 new file mode 100644 index 000000000..e67f3c980 --- /dev/null +++ b/test/unit7/stdlib/time/time.c3 @@ -0,0 +1,29 @@ +module nanoduration_test @test; +import std::io; +import std::time; + +fn void to_format() +{ + char[32] buffer; + char[] buf; + buf = io::bprintf(buffer[..], "%s", (NanoDuration)123)!!; + assert(buf == "123ns", "got %s; want 123ns", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)123_000)!!; + assert(buf == "123µs", "got %s; want 123µs", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)123_000_000)!!; + assert(buf == "123ms", "got %s; want 123ms", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)13_000_000_000)!!; + assert(buf == "13s", "got %s; want 13s", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)123_000_000_000)!!; + assert(buf == "2m3s", "got %s; want 2m3s", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)12345_000_000_000)!!; + assert(buf == "3h25m45s", "got %s; want 3h25m45s", buf); + + buf = io::bprintf(buffer[..], "%s", (NanoDuration)12_100_000_000)!!; + assert(buf == "12.1s", "got %s; want 12.1s", buf); +} \ No newline at end of file