From 3b632b4d9066d9df2e2f8e28d95567c23d5fd1b7 Mon Sep 17 00:00:00 2001 From: alec hodgkinson Date: Mon, 22 Apr 2024 15:13:52 -0700 Subject: [PATCH 001/151] Fixed typo in raylib documentation --- vendor/raylib/raylib.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index b98770271..1d9451b3d 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -951,8 +951,8 @@ foreign lib { SetWindowTitle :: proc(title: cstring) --- // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) SetWindowPosition :: proc(x, y: c.int) --- // Set window position on screen (only PLATFORM_DESKTOP) SetWindowMonitor :: proc(monitor: c.int) --- // Set monitor for the current window - SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) - SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) + SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for WINDOW_RESIZABLE) + SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for WINDOW_RESIZABLE) SetWindowSize :: proc(width, height: c.int) --- // Set window dimensions SetWindowOpacity :: proc(opacity: f32) --- // Set window opacity [0.0f..1.0f] (only PLATFORM_DESKTOP) SetWindowFocused :: proc() --- // Set window focused (only PLATFORM_DESKTOP) From 7934e92d14de6166791e1483f986eb97fe06f595 Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 01:04:41 +0200 Subject: [PATCH 002/151] Initial dependency file generation --- src/build_settings.cpp | 1 + src/main.cpp | 52 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index d9454ba9b..400e478fe 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -831,6 +831,7 @@ struct BuildContext { bool show_timings; TimingsExportFormat export_timings_format; String export_timings_file; + String export_dependencies_file; bool show_unused; bool show_unused_with_location; bool show_more_timings; diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..d21eb21cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -234,6 +234,7 @@ enum BuildFlagKind { BuildFlag_ShowMoreTimings, BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, + BuildFlag_ExportDependencies, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -320,7 +321,6 @@ enum BuildFlagKind { BuildFlag_Subsystem, #endif - BuildFlag_COUNT, }; @@ -427,6 +427,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); @@ -753,6 +754,19 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_ExportDependencies: { + GB_ASSERT(value.kind == ExactValue_String); + + String export_path = string_trim_whitespace(value.value_string); + if (is_build_flag_path_valid(export_path)) { + build_context.export_dependencies_file = path_to_full_path(heap_allocator(), export_path); + } else { + gb_printf_err("Invalid -export-dependencies path, got %.*s\n", LIT(export_path)); + bad_flags = true; + } + + break; + } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); build_context.show_system_calls = true; @@ -1790,6 +1804,11 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Example: -export-timings-file:timings.json"); print_usage_line(0, ""); + print_usage_line(1, "-export-dependencies:"); + print_usage_line(2, "Exports all source files used to create the output file in Make format."); + print_usage_line(2, "Example: -export-dependencies:odin.d"); + print_usage_line(0, ""); + print_usage_line(1, "-thread-count:"); print_usage_line(2, "Overrides the number of threads the compiler will use to compile with."); print_usage_line(2, "Example: -thread-count:2"); @@ -2916,10 +2935,35 @@ int main(int arg_count, char const **arg_ptr) { show_timings(checker, &global_timings); } - if (run_output) { - String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); - defer (gb_free(heap_allocator(), exe_name.text)); + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + if (build_context.export_dependencies_file.len > 0) { + gbFile f = {}; + char * fileName = (char *)build_context.export_dependencies_file.text; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); + if (err != gbFileError_None) { + gb_printf_err("Failed to export dependencies to: %s\n", fileName); + exit_with_errors(); + return 1; + } + defer (gb_file_close(&f)); + + gb_fprintf(&f, "%.*s:", LIT(exe_name)); + + for (isize i = parser->packages.count-1; i >= 0; i--) { + AstPackage *pkg = parser->packages[i]; + for (isize j = pkg->files.count-1; j >= 0; j--) { + AstFile *file = pkg->files[j]; + + gb_fprintf(&f, " \\\n %.*s", LIT(file->fullpath)); + } + } + + gb_fprintf(&f, "\n"); + } + + if (run_output) { return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; From 8664b88c8fe282aaabed6c3522802b63e39bcf02 Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 01:20:45 +0200 Subject: [PATCH 003/151] Improved depdendency formatting to support paths containing spaces --- src/main.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d21eb21cb..575745290 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2951,12 +2951,31 @@ int main(int arg_count, char const **arg_ptr) { gb_fprintf(&f, "%.*s:", LIT(exe_name)); + isize current_line_length = exe_name.len + 1; + for (isize i = parser->packages.count-1; i >= 0; i--) { AstPackage *pkg = parser->packages[i]; for (isize j = pkg->files.count-1; j >= 0; j--) { AstFile *file = pkg->files[j]; - gb_fprintf(&f, " \\\n %.*s", LIT(file->fullpath)); + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } + + gb_file_write(&f, " ", 1); + current_line_length++; + + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); + current_line_length++; + } + gb_file_write(&f, &part, 1); + current_line_length++; + } } } From 0cba33075ff6d2b5059e8f6c6d2f5a7c92f02edf Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 13:44:38 +0200 Subject: [PATCH 004/151] Add the option to export dependencies as JSON. --- src/build_settings.cpp | 7 ++ src/main.cpp | 164 ++++++++++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 51 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 400e478fe..3bf168177 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -694,6 +694,12 @@ enum TimingsExportFormat : i32 { TimingsExportCSV = 2, }; +enum DependenciesExportFormat : i32 { + DependenciesExportUnspecified = 0, + DependenciesExportMake = 1, + DependenciesExportJson = 2, +}; + enum ErrorPosStyle { ErrorPosStyle_Default, // path(line:column) msg ErrorPosStyle_Unix, // path:line:column: msg @@ -831,6 +837,7 @@ struct BuildContext { bool show_timings; TimingsExportFormat export_timings_format; String export_timings_file; + DependenciesExportFormat export_dependencies_format; String export_dependencies_file; bool show_unused; bool show_unused_with_location; diff --git a/src/main.cpp b/src/main.cpp index 575745290..bceee83da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -235,6 +235,7 @@ enum BuildFlagKind { BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, BuildFlag_ExportDependencies, + BuildFlag_ExportDependenciesFile, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -427,7 +428,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_ExportDependenciesFile, str_lit("export-dependencies-file"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); @@ -757,6 +759,23 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_ExportDependencies: { GB_ASSERT(value.kind == ExactValue_String); + if (value.value_string == "make") { + build_context.export_dependencies_format = DependenciesExportMake; + } else if (value.value_string == "json") { + build_context.export_dependencies_format = DependenciesExportJson; + } else { + gb_printf_err("Invalid export format for -export-dependencies:, got %.*s\n", LIT(value.value_string)); + gb_printf_err("Valid export formats:\n"); + gb_printf_err("\tmake\n"); + gb_printf_err("\tjson\n"); + bad_flags = true; + } + + break; + } + case BuildFlag_ExportDependenciesFile: { + GB_ASSERT(value.kind == ExactValue_String); + String export_path = string_trim_whitespace(value.value_string); if (is_build_flag_path_valid(export_path)) { build_context.export_dependencies_file = path_to_full_path(heap_allocator(), export_path); @@ -764,8 +783,8 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("Invalid -export-dependencies path, got %.*s\n", LIT(export_path)); bad_flags = true; } - - break; + + break; } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); @@ -1649,6 +1668,74 @@ gb_internal void show_timings(Checker *c, Timings *t) { } } +gb_internal void export_dependencies(Parser *p) { + GB_ASSERT(build_context.export_dependencies_format != DependenciesExportUnspecified); + + if (build_context.export_dependencies_file.len <= 0) { + gb_printf_err("No dependency file specified with `-export-dependencies-file`\n"); + exit_with_errors(); + return; + } + + gbFile f = {}; + char * fileName = (char *)build_context.export_dependencies_file.text; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); + if (err != gbFileError_None) { + gb_printf_err("Failed to export dependencies to: %s\n", fileName); + exit_with_errors(); + return; + } + defer (gb_file_close(&f)); + + if (build_context.export_dependencies_format == DependenciesExportMake) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + + gb_fprintf(&f, "%.*s:", LIT(exe_name)); + + isize current_line_length = exe_name.len + 1; + + for(AstPackage *pkg : p->packages) { + for(AstFile *file : pkg->files) { + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } + + gb_file_write(&f, " ", 1); + current_line_length++; + + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); + current_line_length++; + } + gb_file_write(&f, &part, 1); + current_line_length++; + } + } + } + + gb_fprintf(&f, "\n"); + } else if (build_context.export_dependencies_format == DependenciesExportJson) { + gb_fprintf(&f, "{\n"); + + gb_fprintf(&f, "\t\"source_files\": [\n"); + + for(AstPackage *pkg : p->packages) { + for(AstFile *file : pkg->files) { + gb_fprintf(&f, "\t\t\"%.*s\",\n", LIT(file->fullpath)); + } + } + + gb_fprintf(&f, "\t],\n"); + + gb_fprintf(&f, "}\n"); + } +} + gb_internal void remove_temp_files(lbGenerator *gen) { if (build_context.keep_temp_files) return; @@ -1804,9 +1891,16 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Example: -export-timings-file:timings.json"); print_usage_line(0, ""); - print_usage_line(1, "-export-dependencies:"); - print_usage_line(2, "Exports all source files used to create the output file in Make format."); - print_usage_line(2, "Example: -export-dependencies:odin.d"); + print_usage_line(1, "-export-dependencies:"); + print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + print_usage_line(0, ""); + + print_usage_line(1, "-export-dependencies-file:"); + print_usage_line(2, "Specifies the filename for `-export-dependencies`."); + print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); print_usage_line(0, ""); print_usage_line(1, "-thread-count:"); @@ -2900,6 +2994,10 @@ int main(int arg_count, char const **arg_ptr) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); + } return result; } break; @@ -2922,6 +3020,10 @@ int main(int arg_count, char const **arg_ptr) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); + } return result; } break; @@ -2935,54 +3037,14 @@ int main(int arg_count, char const **arg_ptr) { show_timings(checker, &global_timings); } - String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); - defer (gb_free(heap_allocator(), exe_name.text)); - - if (build_context.export_dependencies_file.len > 0) { - gbFile f = {}; - char * fileName = (char *)build_context.export_dependencies_file.text; - gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); - if (err != gbFileError_None) { - gb_printf_err("Failed to export dependencies to: %s\n", fileName); - exit_with_errors(); - return 1; - } - defer (gb_file_close(&f)); - - gb_fprintf(&f, "%.*s:", LIT(exe_name)); - - isize current_line_length = exe_name.len + 1; - - for (isize i = parser->packages.count-1; i >= 0; i--) { - AstPackage *pkg = parser->packages[i]; - for (isize j = pkg->files.count-1; j >= 0; j--) { - AstFile *file = pkg->files[j]; - - /* Arbitrary line break value. Maybe make this better? */ - if (current_line_length >= 80-2) { - gb_file_write(&f, " \\\n ", 4); - current_line_length = 1; - } - - gb_file_write(&f, " ", 1); - current_line_length++; - - for (isize k = 0; k < file->fullpath.len; k++) { - char part = file->fullpath.text[k]; - if (part == ' ') { - gb_file_write(&f, "\\", 1); - current_line_length++; - } - gb_file_write(&f, &part, 1); - current_line_length++; - } - } - } - - gb_fprintf(&f, "\n"); + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); } if (run_output) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; From 2ecf909be02f719596f566853d13666d0db4da8d Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 27 May 2024 11:54:28 -0400 Subject: [PATCH 005/151] Fix open() foreign libc signature on Darwin --- core/os/os_darwin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index a688e1ac3..877a90bf1 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -442,7 +442,7 @@ F_GETPATH :: 50 // return the full path of the fd foreign libc { @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle --- @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- From 8a521648b9f7e1ba29c64006b6469cbf2095db68 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 31 May 2024 20:54:20 +0200 Subject: [PATCH 006/151] wasm: fix runtime.js even more for wasm64p32 - make the int size configurable in the `runWasm` call, no more constants to hunt down and change - make storeU64 and storeI64 handle bigints, this is needed in the odin_dom library - fix alignment issues within init_event_raw --- vendor/wasm/js/runtime.js | 94 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index f3df85648..5c7f97fae 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -13,14 +13,18 @@ function stripNewline(str) { return str.replace(/\n/, ' ') } -const INT_SIZE = 4; // NOTE: set to `8` if the target has 64 bit ints (`wasm64p32` for example). -const STRING_SIZE = 2*INT_SIZE; - class WasmMemoryInterface { constructor() { this.memory = null; this.exports = null; this.listenerMap = {}; + + // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + this.intSize = 4; + } + + setIntSize(size) { + this.intSize = size; } setMemory(memory) { @@ -73,21 +77,21 @@ class WasmMemoryInterface { loadF32(addr) { return this.mem.getFloat32(addr, true); } loadF64(addr) { return this.mem.getFloat64(addr, true); } loadInt(addr) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { return this.loadI64(addr); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { return this.loadI32(addr); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } }; loadUint(addr) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { return this.loadU64(addr); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { return this.loadU32(addr); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } }; loadPtr(addr) { return this.loadU32(addr); } @@ -108,31 +112,43 @@ class WasmMemoryInterface { storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } storeU64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true); } storeI64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setInt32 (addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true); } storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } storeInt(addr, value) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { this.storeI64(addr, value); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { this.storeI32(addr, value); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } } storeUint(addr, value) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { this.storeU64(addr, value); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { this.storeU32(addr, value); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } } @@ -241,10 +257,11 @@ class WebGLInterface { } } getSource(shader, strings_ptr, strings_length) { + const stringSize = this.mem.intSize*2; let source = ""; for (let i = 0; i < strings_length; i++) { - let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE); - let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4); + let ptr = this.mem.loadPtr(strings_ptr + i*stringSize); + let len = this.mem.loadPtr(strings_ptr + i*stringSize + 4); let str = this.mem.loadString(ptr, len); source += str; } @@ -1151,10 +1168,11 @@ class WebGLInterface { }, TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { this.assertWebGL2(); + const stringSize = this.mem.intSize*2; let varyings = []; for (let i = 0; i < varyings_len; i++) { - let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4); - let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4); + let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4); + let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 1*4); varyings.push(this.mem.loadString(ptr, len)); } this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); @@ -1393,7 +1411,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, "odin_dom": { init_event_raw: (ep) => { - const W = 4; + const W = wasmMemoryInterface.intSize; let offset = ep; let off = (amount, alignment) => { if (alignment === undefined) { @@ -1407,6 +1425,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { return x; }; + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + let wmi = wasmMemoryInterface; let e = event_temp_data.event; @@ -1427,10 +1452,12 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU32(off(4), 0); } - wmi.storeUint(off(W), event_temp_data.id_ptr); - wmi.storeUint(off(W), event_temp_data.id_len); - wmi.storeUint(off(W), 0); // padding + align(W); + wmi.storeI32(off(W), event_temp_data.id_ptr); + wmi.storeUint(off(W), event_temp_data.id_len); + + align(8); wmi.storeF64(off(8), e.timeStamp*1e-3); wmi.storeU8(off(1), e.eventPhase); @@ -1442,7 +1469,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU8(off(1), !!e.isComposing); wmi.storeU8(off(1), !!e.isTrusted); - let base = off(0, 8); + align(8); if (e instanceof WheelEvent) { wmi.storeF64(off(8), e.deltaX); wmi.storeF64(off(8), e.deltaY); @@ -1689,8 +1716,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }; }; -async function runWasm(wasmPath, consoleElement, extraForeignImports) { - let wasmMemoryInterface = new WasmMemoryInterface(); +/** + * @param {string} wasmPath - Path to the WASM module to run + * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console + * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module + * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + */ +async function runWasm(wasmPath, consoleElement, extraForeignImports, intSize = 4) { + const wasmMemoryInterface = new WasmMemoryInterface(); + wasmMemoryInterface.setIntSize(intSize); let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); let exports = {}; From 451dc645df9f12058267d7d5ae0f8ac8a1fb8998 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Fri, 31 May 2024 21:55:40 +0200 Subject: [PATCH 007/151] Add unreachable to base/builtin/builtin.odin --- base/builtin/builtin.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin index 5cba3c8ea..c4a9b141f 100644 --- a/base/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -126,3 +126,5 @@ clamp :: proc(value, minimum, maximum: T) -> T --- soa_zip :: proc(slices: ...) -> #soa[]Struct --- soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) --- + +unreachable :: proc() -> ! --- From f49575f1fbb0009b57d98cdacb90f2fed6b2c075 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 18 May 2024 17:55:48 +0900 Subject: [PATCH 008/151] core/simd/x86: Add the AES-NI intrinsics --- core/simd/x86/aes.odin | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 core/simd/x86/aes.odin diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin new file mode 100644 index 000000000..3a32de0d6 --- /dev/null +++ b/core/simd/x86/aes.odin @@ -0,0 +1,49 @@ +//+build i386, amd64 +package simd_x86 + +@(require_results, enable_target_feature = "aes") +_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdec(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdeclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenc(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i { + return aesimc(a) +} + +@(require_results, enable_target_feature = "aes") +_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { + return aeskeygenassist(a, u8(IMM8)) +} + + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.x86.aesni.aesdec") + aesdec :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesdeclast") + aesdeclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenc") + aesenc :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenclast") + aesenclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesimc") + aesimc :: proc(a: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aeskeygenassist") + aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i --- +} From cba58924a895822d8160c957b0e859a358a29391 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 20 May 2024 21:46:18 +0900 Subject: [PATCH 009/151] core/crypto/_aes: 64-bit portable implementation --- core/crypto/_aes/aes.odin | 22 ++ core/crypto/_aes/ct64/api.odin | 96 ++++++++ core/crypto/_aes/ct64/ct64.odin | 265 +++++++++++++++++++++++ core/crypto/_aes/ct64/ct64_dec.odin | 135 ++++++++++++ core/crypto/_aes/ct64/ct64_enc.odin | 95 ++++++++ core/crypto/_aes/ct64/ct64_keysched.odin | 179 +++++++++++++++ core/crypto/_aes/ct64/helpers.odin | 75 +++++++ 7 files changed, 867 insertions(+) create mode 100644 core/crypto/_aes/aes.odin create mode 100644 core/crypto/_aes/ct64/api.odin create mode 100644 core/crypto/_aes/ct64/ct64.odin create mode 100644 core/crypto/_aes/ct64/ct64_dec.odin create mode 100644 core/crypto/_aes/ct64/ct64_enc.odin create mode 100644 core/crypto/_aes/ct64/ct64_keysched.odin create mode 100644 core/crypto/_aes/ct64/helpers.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin new file mode 100644 index 000000000..74906fcd4 --- /dev/null +++ b/core/crypto/_aes/aes.odin @@ -0,0 +1,22 @@ +package _aes + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: 16 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: 24 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: 32 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: 16 + + +// ROUNDS_128 is the number of rounds for AES-128. +ROUNDS_128 :: 10 +// ROUNDS_192 is the number of rounds for AES-192. +ROUNDS_192 :: 12 +// ROUNDS_256 is the number of rounds for AES-256. +ROUNDS_256 :: 14 + +// RCON is the AES keyschedule round constants. +RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/api.odin b/core/crypto/_aes/ct64/api.odin new file mode 100644 index 000000000..ae624971c --- /dev/null +++ b/core/crypto/_aes/ct64/api.odin @@ -0,0 +1,96 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:mem" + +STRIDE :: 4 + +// Context is a keyed AES (ECB) instance. +Context :: struct { + _sk_exp: [120]u64, + _num_rounds: int, + _is_initialized: bool, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + skey: [30]u64 = --- + + ctx._num_rounds = keysched(skey[:], key) + skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) + ctx._is_initialized = true +} + +// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. +encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + assert(ctx._is_initialized) + + q: [8]u64 + load_blockx1(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. +decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + assert(ctx._is_initialized) + + q: [8]u64 + load_blockx1(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. +encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + assert(ctx._is_initialized) + + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. +decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + assert(ctx._is_initialized) + + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + mem.zero_explicit(ctx, size_of(ctx)) +} diff --git a/core/crypto/_aes/ct64/ct64.odin b/core/crypto/_aes/ct64/ct64.odin new file mode 100644 index 000000000..f198cab81 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64.odin @@ -0,0 +1,265 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +// Bitsliced AES for 64-bit general purpose (integer) registers. Each +// invocation will process up to 4 blocks at a time. This implementation +// is derived from the BearSSL ct64 code, and distributed under a 1-clause +// BSD license with permission from the original author. +// +// WARNING: "hic sunt dracones" +// +// This package also deliberately exposes enough internals to be able to +// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to +// allow the implementation of non-AES primitives that use the AES round +// function such as AEGIS and Deoxys-II. This should ONLY be done when +// implementing something other than AES itself. + +sub_bytes :: proc "contextless" (q: ^[8]u64) { + // This S-box implementation is a straightforward translation of + // the circuit described by Boyar and Peralta in "A new + // combinational logic minimization technique with applications + // to cryptology" (https://eprint.iacr.org/2009/191.pdf). + // + // Note that variables x* (input) and s* (output) are numbered + // in "reverse" order (x0 is the high bit, x7 is the low bit). + + x0 := q[7] + x1 := q[6] + x2 := q[5] + x3 := q[4] + x4 := q[3] + x5 := q[2] + x6 := q[1] + x7 := q[0] + + // Top linear transformation. + y14 := x3 ~ x5 + y13 := x0 ~ x6 + y9 := x0 ~ x3 + y8 := x0 ~ x5 + t0 := x1 ~ x2 + y1 := t0 ~ x7 + y4 := y1 ~ x3 + y12 := y13 ~ y14 + y2 := y1 ~ x0 + y5 := y1 ~ x6 + y3 := y5 ~ y8 + t1 := x4 ~ y12 + y15 := t1 ~ x5 + y20 := t1 ~ x1 + y6 := y15 ~ x7 + y10 := y15 ~ t0 + y11 := y20 ~ y9 + y7 := x7 ~ y11 + y17 := y10 ~ y11 + y19 := y10 ~ y8 + y16 := t0 ~ y11 + y21 := y13 ~ y16 + y18 := x0 ~ y16 + + // Non-linear section. + t2 := y12 & y15 + t3 := y3 & y6 + t4 := t3 ~ t2 + t5 := y4 & x7 + t6 := t5 ~ t2 + t7 := y13 & y16 + t8 := y5 & y1 + t9 := t8 ~ t7 + t10 := y2 & y7 + t11 := t10 ~ t7 + t12 := y9 & y11 + t13 := y14 & y17 + t14 := t13 ~ t12 + t15 := y8 & y10 + t16 := t15 ~ t12 + t17 := t4 ~ t14 + t18 := t6 ~ t16 + t19 := t9 ~ t14 + t20 := t11 ~ t16 + t21 := t17 ~ y20 + t22 := t18 ~ y19 + t23 := t19 ~ y21 + t24 := t20 ~ y18 + + t25 := t21 ~ t22 + t26 := t21 & t23 + t27 := t24 ~ t26 + t28 := t25 & t27 + t29 := t28 ~ t22 + t30 := t23 ~ t24 + t31 := t22 ~ t26 + t32 := t31 & t30 + t33 := t32 ~ t24 + t34 := t23 ~ t33 + t35 := t27 ~ t33 + t36 := t24 & t35 + t37 := t36 ~ t34 + t38 := t27 ~ t36 + t39 := t29 & t38 + t40 := t25 ~ t39 + + t41 := t40 ~ t37 + t42 := t29 ~ t33 + t43 := t29 ~ t40 + t44 := t33 ~ t37 + t45 := t42 ~ t41 + z0 := t44 & y15 + z1 := t37 & y6 + z2 := t33 & x7 + z3 := t43 & y16 + z4 := t40 & y1 + z5 := t29 & y7 + z6 := t42 & y11 + z7 := t45 & y17 + z8 := t41 & y10 + z9 := t44 & y12 + z10 := t37 & y3 + z11 := t33 & y4 + z12 := t43 & y13 + z13 := t40 & y5 + z14 := t29 & y2 + z15 := t42 & y9 + z16 := t45 & y14 + z17 := t41 & y8 + + // Bottom linear transformation. + t46 := z15 ~ z16 + t47 := z10 ~ z11 + t48 := z5 ~ z13 + t49 := z9 ~ z10 + t50 := z2 ~ z12 + t51 := z2 ~ z5 + t52 := z7 ~ z8 + t53 := z0 ~ z3 + t54 := z6 ~ z7 + t55 := z16 ~ z17 + t56 := z12 ~ t48 + t57 := t50 ~ t53 + t58 := z4 ~ t46 + t59 := z3 ~ t54 + t60 := t46 ~ t57 + t61 := z14 ~ t57 + t62 := t52 ~ t58 + t63 := t49 ~ t58 + t64 := z4 ~ t59 + t65 := t61 ~ t62 + t66 := z1 ~ t63 + s0 := t59 ~ t63 + s6 := t56 ~ ~t62 + s7 := t48 ~ ~t60 + t67 := t64 ~ t65 + s3 := t53 ~ t66 + s4 := t51 ~ t66 + s5 := t47 ~ t65 + s1 := t64 ~ ~s3 + s2 := t55 ~ ~t67 + + q[7] = s0 + q[6] = s1 + q[5] = s2 + q[4] = s3 + q[3] = s4 + q[2] = s5 + q[1] = s6 + q[0] = s7 +} + +orthogonalize :: proc "contextless" (q: ^[8]u64) { + CL2 :: 0x5555555555555555 + CH2 :: 0xAAAAAAAAAAAAAAAA + q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2) + q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2) + q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2) + q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2) + + CL4 :: 0x3333333333333333 + CH4 :: 0xCCCCCCCCCCCCCCCC + q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4) + q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4) + q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4) + q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4) + + CL8 :: 0x0F0F0F0F0F0F0F0F + CH8 :: 0xF0F0F0F0F0F0F0F0 + q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8) + q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8) + q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8) + q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8) +} + +@(require_results) +interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check { + if len(w) < 4 { + intrinsics.trap() + } + x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3]) + x0 |= (x0 << 16) + x1 |= (x1 << 16) + x2 |= (x2 << 16) + x3 |= (x3 << 16) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + x0 |= (x0 << 8) + x1 |= (x1 << 8) + x2 |= (x2 << 8) + x3 |= (x3 << 8) + x0 &= 0x00FF00FF00FF00FF + x1 &= 0x00FF00FF00FF00FF + x2 &= 0x00FF00FF00FF00FF + x3 &= 0x00FF00FF00FF00FF + q0 = x0 | (x2 << 8) + q1 = x1 | (x3 << 8) + return +} + +@(require_results) +interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) { + x0 := q0 & 0x00FF00FF00FF00FF + x1 := q1 & 0x00FF00FF00FF00FF + x2 := (q0 >> 8) & 0x00FF00FF00FF00FF + x3 := (q1 >> 8) & 0x00FF00FF00FF00FF + x0 |= (x0 >> 8) + x1 |= (x1 >> 8) + x2 |= (x2 >> 8) + x3 |= (x3 >> 8) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + w0 = u32(x0) | u32(x0 >> 16) + w1 = u32(x1) | u32(x1 >> 16) + w2 = u32(x2) | u32(x2 >> 16) + w3 = u32(x3) | u32(x3 >> 16) + return +} + +@(private) +rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 { + return (x << 32) | (x >> 32) +} diff --git a/core/crypto/_aes/ct64/ct64_dec.odin b/core/crypto/_aes/ct64/ct64_dec.odin new file mode 100644 index 000000000..408ee6002 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_dec.odin @@ -0,0 +1,135 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +inv_sub_bytes :: proc "contextless" (q: ^[8]u64) { + // AES S-box is: + // S(x) = A(I(x)) ^ 0x63 + // where I() is inversion in GF(256), and A() is a linear + // transform (0 is formally defined to be its own inverse). + // Since inversion is an involution, the inverse S-box can be + // computed from the S-box as: + // iS(x) = B(S(B(x ^ 0x63)) ^ 0x63) + // where B() is the inverse of A(). Indeed, for any y in GF(256): + // iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y + // + // Note: we reuse the implementation of the forward S-box, + // instead of duplicating it here, so that total code size is + // lower. By merging the B() transforms into the S-box circuit + // we could make faster CBC decryption, but CBC decryption is + // already quite faster than CBC encryption because we can + // process four blocks in parallel. + + q0 := ~q[0] + q1 := ~q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := ~q[5] + q6 := ~q[6] + q7 := q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 + + sub_bytes(q) + + q0 = ~q[0] + q1 = ~q[1] + q2 = q[2] + q3 = q[3] + q4 = q[4] + q5 = ~q[5] + q6 = ~q[6] + q7 = q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 +} + +inv_shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x000000000FFF0000) << 4) | + ((x & 0x00000000F0000000) >> 12) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000F000000000000) << 12) | + ((x & 0xFFF0000000000000) >> 4) + } +} + +inv_mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5) + q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6) + q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7) + q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7) + q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6) + q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7) + q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7) + q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7) +} + +@(private) +_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey[num_rounds << 3:]) + for u := num_rounds - 1; u > 0; u -= 1 { + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey[u << 3:]) + inv_mix_columns(q) + } + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey) +} diff --git a/core/crypto/_aes/ct64/ct64_enc.odin b/core/crypto/_aes/ct64/ct64_enc.odin new file mode 100644 index 000000000..36d4aebc8 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_enc.odin @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check { + if len(sk) < 8 { + intrinsics.trap() + } + + q[0] ~= sk[0] + q[1] ~= sk[1] + q[2] ~= sk[2] + q[3] ~= sk[3] + q[4] ~= sk[4] + q[5] ~= sk[5] + q[6] ~= sk[6] + q[7] ~= sk[7] +} + +shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x00000000FFF00000) >> 4) | + ((x & 0x00000000000F0000) << 12) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0xF000000000000000) >> 12) | + ((x & 0x0FFF000000000000) << 4) + } +} + +mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0) + q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1) + q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2) + q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3) + q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4) + q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5) + q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6) + q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7) +} + +@(private) +_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey) + for u in 1 ..< num_rounds { + sub_bytes(q) + shift_rows(q) + mix_columns(q) + add_round_key(q, skey[u << 3:]) + } + sub_bytes(q) + shift_rows(q) + add_round_key(q, skey[num_rounds << 3:]) +} diff --git a/core/crypto/_aes/ct64/ct64_keysched.odin b/core/crypto/_aes/ct64/ct64_keysched.odin new file mode 100644 index 000000000..060a2c03e --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_keysched.odin @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" +import "core:mem" + +@(private, require_results) +sub_word :: proc "contextless" (x: u32) -> u32 { + q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0} + + orthogonalize(&q) + sub_bytes(&q) + orthogonalize(&q) + ret := u32(q[0]) + + mem.zero_explicit(&q[0], size_of(u64)) + + return ret +} + +@(private, require_results) +keysched :: proc(comp_skey: []u64, key: []byte) -> int { + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + + skey: [60]u32 = --- + nk, nkf := key_len >> 2, (num_rounds + 1) << 2 + for i in 0 ..< nk { + skey[i] = endian.unchecked_get_u32le(key[i << 2:]) + } + tmp := skey[(key_len >> 2) - 1] + for i, j, k := nk, 0, 0; i < nkf; i += 1 { + if j == 0 { + tmp = (tmp << 24) | (tmp >> 8) + tmp = sub_word(tmp) ~ u32(_aes.RCON[k]) + } else if nk > 6 && j == 4 { + tmp = sub_word(tmp) + } + tmp ~= skey[i - nk] + skey[i] = tmp + if j += 1; j == nk { + j = 0 + k += 1 + } + } + + q: [8]u64 = --- + for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 { + q[0], q[4] = interleave_in(skey[i:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + comp_skey[j + 0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[j + 1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + + return num_rounds +} + +@(private) +skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) { + n := (num_rounds + 1) << 1 + for u, v := 0, 0; u < n; u, v = u + 1, v + 4 { + x0 := comp_skey[u] + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + skey[v + 0] = (x0 << 4) - x0 + skey[v + 1] = (x1 << 4) - x1 + skey[v + 2] = (x2 << 4) - x2 + skey[v + 3] = (x3 << 4) - x3 + } +} + +orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) { + if len(qq) < 8 || len(key) != 16 { + intrinsics.trap() + } + + skey: [4]u32 = --- + skey[0] = endian.unchecked_get_u32le(key[0:]) + skey[1] = endian.unchecked_get_u32le(key[4:]) + skey[2] = endian.unchecked_get_u32le(key[8:]) + skey[3] = endian.unchecked_get_u32le(key[12:]) + + q: [8]u64 = --- + q[0], q[4] = interleave_in(skey[:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + + comp_skey: [2]u64 = --- + comp_skey[0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + + for x, u in comp_skey { + x0 := x + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + qq[u * 4 + 0] = (x0 << 4) - x0 + qq[u * 4 + 1] = (x1 << 4) - x1 + qq[u * 4 + 2] = (x2 << 4) - x2 + qq[u * 4 + 3] = (x3 << 4) - x3 + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + mem.zero_explicit(&comp_skey, size_of(comp_skey)) +} diff --git a/core/crypto/_aes/ct64/helpers.odin b/core/crypto/_aes/ct64/helpers.odin new file mode 100644 index 000000000..169271f6d --- /dev/null +++ b/core/crypto/_aes/ct64/helpers.odin @@ -0,0 +1,75 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) { + if len(src) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w: [4]u32 = --- + w[0] = endian.unchecked_get_u32le(src[0:]) + w[1] = endian.unchecked_get_u32le(src[4:]) + w[2] = endian.unchecked_get_u32le(src[8:]) + w[3] = endian.unchecked_get_u32le(src[12:]) + q[0], q[4] = interleave_in(w[:]) + orthogonalize(q) +} + +store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) { + if len(dst) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + orthogonalize(q) + w0, w1, w2, w3 := interleave_out(q[0], q[4]) + endian.unchecked_put_u32le(dst[0:], w0) + endian.unchecked_put_u32le(dst[4:], w1) + endian.unchecked_put_u32le(dst[8:], w2) + endian.unchecked_put_u32le(dst[12:], w3) +} + +load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) { + if n := len(src); n > STRIDE || n == 0 { + intrinsics.trap() + } + + w: [4]u32 = --- + for s, i in src { + if len(s) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w[0] = endian.unchecked_get_u32le(s[0:]) + w[1] = endian.unchecked_get_u32le(s[4:]) + w[2] = endian.unchecked_get_u32le(s[8:]) + w[3] = endian.unchecked_get_u32le(s[12:]) + q[i], q[i + 4] = interleave_in(w[:]) + } + orthogonalize(q) +} + +store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) { + if n := len(dst); n > STRIDE || n == 0 { + intrinsics.trap() + } + + orthogonalize(q) + for d, i in dst { + // Allow storing [0,4] blocks. + if d == nil { + break + } + if len(d) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w0, w1, w2, w3 := interleave_out(q[i], q[i + 4]) + endian.unchecked_put_u32le(d[0:], w0) + endian.unchecked_put_u32le(d[4:], w1) + endian.unchecked_put_u32le(d[8:], w2) + endian.unchecked_put_u32le(d[12:], w3) + } +} From 1ade62b630f565d975049aa75935c64db175ef61 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 27 May 2024 21:25:26 +0900 Subject: [PATCH 010/151] core/crypto/_aes/ct64: Add GHASH --- core/crypto/_aes/aes.odin | 5 ++ core/crypto/_aes/ct64/ghash.odin | 136 +++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 core/crypto/_aes/ct64/ghash.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin index 74906fcd4..6b290feb6 100644 --- a/core/crypto/_aes/aes.odin +++ b/core/crypto/_aes/aes.odin @@ -18,5 +18,10 @@ ROUNDS_192 :: 12 // ROUNDS_256 is the number of rounds for AES-256. ROUNDS_256 :: 14 +// GHASH_KEY_SIZE is the GHASH key size in bytes. +GHASH_KEY_SIZE :: 16 +// GHASH_BLOCK_SIZE is the GHASH block size in bytes. +GHASH_BLOCK_SIZE :: 16 + // RCON is the AES keyschedule round constants. RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/ghash.odin b/core/crypto/_aes/ct64/ghash.odin new file mode 100644 index 000000000..21ac2ca97 --- /dev/null +++ b/core/crypto/_aes/ct64/ghash.odin @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +@(private = "file") +bmul64 :: proc "contextless" (x, y: u64) -> u64 { + x0 := x & 0x1111111111111111 + x1 := x & 0x2222222222222222 + x2 := x & 0x4444444444444444 + x3 := x & 0x8888888888888888 + y0 := y & 0x1111111111111111 + y1 := y & 0x2222222222222222 + y2 := y & 0x4444444444444444 + y3 := y & 0x8888888888888888 + z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1) + z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2) + z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3) + z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0) + z0 &= 0x1111111111111111 + z1 &= 0x2222222222222222 + z2 &= 0x4444444444444444 + z3 &= 0x8888888888888888 + return z0 | z1 | z2 | z3 +} + +@(private = "file") +rev64 :: proc "contextless" (x: u64) -> u64 { + x := x + x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555) + x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333) + x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F) + x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF) + x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF) + return (x << 32) | (x >> 32) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +ghash :: proc "contextless" (dst, key, data: []byte) { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + buf := data + l := len(buf) + + y1 := endian.unchecked_get_u64be(dst[0:]) + y0 := endian.unchecked_get_u64be(dst[8:]) + h1 := endian.unchecked_get_u64be(key[0:]) + h0 := endian.unchecked_get_u64be(key[8:]) + h0r := rev64(h0) + h1r := rev64(h1) + h2 := h0 ~ h1 + h2r := h0r ~ h1r + + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + y1 ~= endian.unchecked_get_u64be(src) + y0 ~= endian.unchecked_get_u64be(src[8:]) + + y0r := rev64(y0) + y1r := rev64(y1) + y2 := y0 ~ y1 + y2r := y0r ~ y1r + + z0 := bmul64(y0, h0) + z1 := bmul64(y1, h1) + z2 := bmul64(y2, h2) + z0h := bmul64(y0r, h0r) + z1h := bmul64(y1r, h1r) + z2h := bmul64(y2r, h2r) + z2 ~= z0 ~ z1 + z2h ~= z0h ~ z1h + z0h = rev64(z0h) >> 1 + z1h = rev64(z1h) >> 1 + z2h = rev64(z2h) >> 1 + + v0 := z0 + v1 := z0h ~ z2 + v2 := z1 ~ z2h + v3 := z1h + + v3 = (v3 << 1) | (v2 >> 63) + v2 = (v2 << 1) | (v1 >> 63) + v1 = (v1 << 1) | (v0 >> 63) + v0 = (v0 << 1) + + v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7) + v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57) + v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7) + v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57) + + y0 = v2 + y1 = v3 + } + + endian.unchecked_put_u64be(dst[0:], y1) + endian.unchecked_put_u64be(dst[8:], y0) +} From c751e4b2ebe8a2a32aee09c67d7d27872fc2d5e3 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 25 May 2024 20:24:39 +0900 Subject: [PATCH 011/151] core/crypto/aes: Add AES implementation --- core/crypto/_aes/aes.odin | 3 +- core/crypto/aes/aes.odin | 22 + core/crypto/aes/aes_ctr.odin | 199 ++++++++ core/crypto/aes/aes_ecb.odin | 57 +++ core/crypto/aes/aes_gcm.odin | 253 ++++++++++ core/crypto/aes/aes_impl.odin | 41 ++ core/crypto/aes/aes_impl_hw_gen.odin | 43 ++ examples/all/all_main.odin | 2 + tests/core/crypto/test_core_crypto.odin | 1 + tests/core/crypto/test_core_crypto_aes.odin | 462 +++++++++++++++++++ tests/core/crypto/test_crypto_benchmark.odin | 60 +++ 11 files changed, 1142 insertions(+), 1 deletion(-) create mode 100644 core/crypto/aes/aes.odin create mode 100644 core/crypto/aes/aes_ctr.odin create mode 100644 core/crypto/aes/aes_ecb.odin create mode 100644 core/crypto/aes/aes_gcm.odin create mode 100644 core/crypto/aes/aes_impl.odin create mode 100644 core/crypto/aes/aes_impl_hw_gen.odin create mode 100644 tests/core/crypto/test_core_crypto_aes.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin index 6b290feb6..4f52485d2 100644 --- a/core/crypto/_aes/aes.odin +++ b/core/crypto/_aes/aes.odin @@ -10,7 +10,6 @@ KEY_SIZE_256 :: 32 // BLOCK_SIZE is the AES block size in bytes. BLOCK_SIZE :: 16 - // ROUNDS_128 is the number of rounds for AES-128. ROUNDS_128 :: 10 // ROUNDS_192 is the number of rounds for AES-192. @@ -22,6 +21,8 @@ ROUNDS_256 :: 14 GHASH_KEY_SIZE :: 16 // GHASH_BLOCK_SIZE is the GHASH block size in bytes. GHASH_BLOCK_SIZE :: 16 +// GHASH_TAG_SIZE is the GHASH tag size in bytes. +GHASH_TAG_SIZE :: 16 // RCON is the AES keyschedule round constants. RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin new file mode 100644 index 000000000..e895c5fe0 --- /dev/null +++ b/core/crypto/aes/aes.odin @@ -0,0 +1,22 @@ +/* +package aes implements the AES block cipher and some common modes. + +See: +- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf +- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +*/ + +package aes + +import "core:crypto/_aes" + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: _aes.KEY_SIZE_128 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: _aes.KEY_SIZE_192 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: _aes.KEY_SIZE_256 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: _aes.BLOCK_SIZE diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin new file mode 100644 index 000000000..1821a7bdf --- /dev/null +++ b/core/crypto/aes/aes_ctr.odin @@ -0,0 +1,199 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// CTR_IV_SIZE is the size of the CTR mode IV in bytes. +CTR_IV_SIZE :: 16 + +// Context_CTR is a keyed AES-CTR instance. +Context_CTR :: struct { + _impl: Context_Impl, + _buffer: [BLOCK_SIZE]byte, + _off: int, + _ctr_hi: u64, + _ctr_lo: u64, + _is_initialized: bool, +} + +// init_ctr initializes a Context_CTR with the provided key and IV. +init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) { + if len(iv) != CTR_IV_SIZE { + panic("crypto/aes: invalid CTR IV size") + } + + init_impl(&ctx._impl, key, impl) + ctx._off = BLOCK_SIZE + ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:]) + ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:]) + ctx._is_initialized = true +} + +// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR +// keystream, and writes the resulting output to dst. dst and src MUST +// alias exactly or not at all. +xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { + assert(ctx._is_initialized) + + // TODO: Enforcing that dst and src alias exactly or not at all + // is a good idea, though odd aliasing should be extremely uncommon. + + src, dst := src, dst + if dst_len := len(dst); dst_len < len(src) { + src = src[:dst_len] + } + + for remaining := len(src); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + ctr_blocks(ctx, dst, src, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + src = src[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_xor := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + for i := 0; i < to_xor; i = i + 1 { + dst[i] = buffered_keystream[i] ~ src[i] + } + ctx._off += to_xor + dst = dst[to_xor:] + src = src[to_xor:] + remaining -= to_xor + } +} + +// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output. +keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) { + assert(ctx._is_initialized) + + dst := dst + for remaining := len(dst); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + ctr_blocks(ctx, dst, nil, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_copy := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + copy(dst[:to_copy], buffered_keystream[:to_copy]) + ctx._off += to_copy + dst = dst[to_copy:] + remaining -= to_copy + } +} + +// reset_ctr sanitizes the Context_CTR. The Context_CTR must be +// re-initialized to be used again. +reset_ctr :: proc "contextless" (ctx: ^Context_CTR) { + reset_impl(&ctx._impl) + ctx._off = 0 + ctx._ctr_hi = 0 + ctx._ctr_lo = 0 + mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) + ctx._is_initialized = false +} + +@(private) +ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { + // Use the optimized hardware implementation if available. + if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + ctr_blocks_hw(ctx, dst, src, nr_blocks) + return + } + + // Portable implementation. + ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) { + endian.unchecked_put_u64be(dst[0:], hi) + endian.unchecked_put_u64be(dst[8:], lo) + + hi, lo := hi, lo + carry: u64 + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return hi, lo + } + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + tmp: [ct64.STRIDE][BLOCK_SIZE]byte = --- + ctrs: [ct64.STRIDE][]byte = --- + for i in 0 ..< ct64.STRIDE { + ctrs[i] = tmp[i][:] + } + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + blocks := ctrs[:n] + + for i in 0 ..< n { + ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo) + } + ct64.encrypt_blocks(impl, blocks, blocks) + + xor_blocks(dst, src, blocks) + + if src != nil { + src = src[n * BLOCK_SIZE:] + } + dst = dst[n * BLOCK_SIZE:] + nr_blocks -= n + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +@(private) +xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) { + // Note: This would be faster `core:simd` was used, however if + // performance of this implementation matters to where that + // optimization would be worth it, use chacha20poly1305, or a + // CPU that isn't e-waste. + if src != nil { + #no_bounds_check { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + for j in 0 ..< BLOCK_SIZE { + blocks[i][j] ~= src[off + j] + } + } + } + } + for i in 0 ..< len(blocks) { + copy(dst[i * BLOCK_SIZE:], blocks[i]) + } +} diff --git a/core/crypto/aes/aes_ecb.odin b/core/crypto/aes/aes_ecb.odin new file mode 100644 index 000000000..498429e29 --- /dev/null +++ b/core/crypto/aes/aes_ecb.odin @@ -0,0 +1,57 @@ +package aes + +import "core:crypto/_aes/ct64" + +// Context_ECB is a keyed AES-ECB instance. +// +// WARNING: Using ECB mode is strongly discouraged unless it is being +// used to implement higher level constructs. +Context_ECB :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_ecb initializes a Context_ECB with the provided key. +init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst. +encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.encrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + encrypt_block_hw(&impl, dst, src) + } +} + +// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst. +decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.decrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + decrypt_block_hw(&impl, dst, src) + } +} + +// reset_ecb sanitizes the Context_ECB. The Context_ECB must be +// re-initialized to be used again. +reset_ecb :: proc "contextless" (ctx: ^Context_ECB) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin new file mode 100644 index 000000000..66ef48db2 --- /dev/null +++ b/core/crypto/aes/aes_gcm.odin @@ -0,0 +1,253 @@ +package aes + +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:mem" + +// GCM_NONCE_SIZE is the size of the GCM nonce in bytes. +GCM_NONCE_SIZE :: 12 +// GCM_TAG_SIZE is the size of a GCM tag in bytes. +GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE + +@(private) +GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes +@(private) +GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes + +// Context_GCM is a keyed AES-GCM instance. +Context_GCM :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_gcm initializes a Context_GCM with the provided key. +init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context_GCM and nonce, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext) + if len(dst) != len(plaintext) { + panic("crypto/aes: invalid destination ciphertext size") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) + return + } + + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_ct64(ctx, &h, &j0, nonce) + + // Note: Our GHASH implementation handles appending padding. + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true) + final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) +} + +// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context_GCM, nonce, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext) + if len(dst) != len(ciphertext) { + panic("crypto/aes: invalid destination plaintext size") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) + } + + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_ct64(ctx, &h, &j0, nonce) + + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false) + final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +// reset_ctr sanitizes the Context_GCM. The Context_GCM must be +// re-initialized to be used again. +reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} + +@(private) +gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { + if len(tag) != GCM_TAG_SIZE { + panic("crypto/aes: invalid GCM tag size") + } + + // The specification supports nonces in the range [1, 2^64) bits + // however per NIST SP 800-38D 5.2.1.1: + // + // > For IVs, it is recommended that implementations restrict support + // > to the length of 96 bits, to promote interoperability, efficiency, + // > and simplicity of design. + if len(nonce) != GCM_NONCE_SIZE { + panic("crypto/aes: invalid GCM nonce size") + } + + if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { + panic("crypto/aes: oversized GCM aad") + } + if text_len := u64(len(text)); text_len > GCM_P_MAX { + panic("crypto/aes: oversized GCM src data") + } +} + +@(private = "file") +init_ghash_ct64 :: proc( + ctx: ^Context_GCM, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + impl := &ctx._impl.(ct64.Context) + + // 1. Let H = CIPH(k, 0^128) + ct64.encrypt_block(impl, h[:], h[:]) + + // ECB encrypt j0, so that we can just XOR with the tag. In theory + // this could be processed along with the final GCTR block, to + // potentially save a call to AES-ECB, but... just use AES-NI. + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + ct64.encrypt_block(impl, j0[:], j0[:]) +} + +@(private = "file") +final_ghash_ct64 :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + ct64.ghash(s[:], h[:], blk[:]) + for i in 0 ..< len(s) { + s[i] ~= j0[i] + } +} + +@(private = "file") +gctr_ct64 :: proc( + ctx: ^Context_GCM, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: []byte, + is_seal: bool, +) { + ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { + endian.unchecked_put_u32be(dst[12:], ctr) + return ctr + 1 + } + + // 2. Define a block J_0 as follows: + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + // + // Note: We only support 96 bit IVs. + tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- + ctrs, blks: [ct64.STRIDE][]byte = ---, --- + ctr: u32 = 2 + for i in 0 ..< ct64.STRIDE { + // Setup scratch space for the keystream. + blks[i] = tmp2[i][:] + + // Pre-copy the IV to all the counter blocks. + ctrs[i] = tmp[i][:] + copy(ctrs[i], nonce) + } + + // We stitch the GCTR and GHASH operations together, so that only + // one pass over the ciphertext is required. + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + l := n * BLOCK_SIZE + + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + // The keystream is written to a separate buffer, as we will + // reuse the first 96-bits of each counter. + for i in 0 ..< n { + ctr = ct64_inc_ctr32(ctrs[i], ctr) + } + ct64.encrypt_blocks(impl, blks[:n], ctrs[:n]) + + xor_blocks(dst, src, blks[:n]) + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + + src = src[l:] + dst = dst[l:] + nr_blocks -= n + } + if l := len(src); l > 0 { + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + ct64_inc_ctr32(ctrs[0], ctr) + ct64.encrypt_block(impl, ctrs[0], ctrs[0]) + + for i in 0 ..< l { + dst[i] = src[i] ~ ctrs[0][i] + } + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + } + + mem.zero_explicit(&tmp, size_of(tmp)) + mem.zero_explicit(&tmp2, size_of(tmp2)) +} diff --git a/core/crypto/aes/aes_impl.odin b/core/crypto/aes/aes_impl.odin new file mode 100644 index 000000000..03747f1fb --- /dev/null +++ b/core/crypto/aes/aes_impl.odin @@ -0,0 +1,41 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:mem" +import "core:reflect" + +@(private) +Context_Impl :: union { + ct64.Context, + Context_Impl_Hardware, +} + +// Implementation is an AES implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available (See `is_hardware_accelerated()`). +Implementation :: enum { + Portable, + Hardware, +} + +@(private) +init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) { + impl := impl + if !is_hardware_accelerated() { + impl = .Portable + } + + switch impl { + case .Portable: + reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context)) + ct64.init(&ctx.(ct64.Context), key) + case .Hardware: + reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware)) + init_impl_hw(&ctx.(Context_Impl_Hardware), key) + } +} + +@(private) +reset_impl :: proc "contextless" (ctx: ^Context_Impl) { + mem.zero_explicit(ctx, size_of(Context_Impl)) +} diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin new file mode 100644 index 000000000..94815f61c --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -0,0 +1,43 @@ +package aes + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return false +} + +@(private) +Context_Impl_Hardware :: struct {} + +@(private) +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + panic(ERR_HW_NOT_SUPPORTED) +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 1077df1ae..6c3972987 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -25,6 +25,7 @@ import rbtree "core:container/rbtree" import topological_sort "core:container/topological_sort" import crypto "core:crypto" +import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" import chacha20 "core:crypto/chacha20" @@ -150,6 +151,7 @@ _ :: rbtree _ :: topological_sort _ :: crypto _ :: crypto_hash +_ :: aes _ :: blake2b _ :: blake2s _ :: chacha20 diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 95db3f292..1f7bcece3 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -33,6 +33,7 @@ main :: proc() { test_kdf(&t) // After hash/mac tests because those should pass first. test_ecc25519(&t) + test_aes(&t) test_chacha20(&t) test_chacha20poly1305(&t) test_sha3_variants(&t) diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin new file mode 100644 index 000000000..f33292a53 --- /dev/null +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -0,0 +1,462 @@ +package test_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:fmt" +import "core:testing" + +import "core:crypto/aes" +import "core:crypto/sha2" + +import tc "tests:common" + +@(test) +test_aes :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + tc.log(t, "Testing AES") + + impls := make([dynamic]aes.Implementation, 0, 2) + append(&impls, aes.Implementation.Portable) + if aes.is_hardware_accelerated() { + append(&impls, aes.Implementation.Hardware) + } + + for impl in impls { + test_aes_ecb(t, impl) + test_aes_ctr(t, impl) + test_aes_gcm(t, impl) + } +} + +@(test) +test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-ECB/%v", impl)) + + test_vectors := []struct { + key: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "6bc1bee22e409f96e93d7e117393172a", + "3ad77bb40d7a3660a89ecaf32466ef97", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "f5d3d58503b9699de785895a96fdbaaf", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "30c81c46a35ce411e5fbc1191a0a52ef", + "43b1cd7f598ece23881b00e3ed030688", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f69f2445df4f9b17ad2b417be66c3710", + "7b0c785e27e8ad3f8223207104725dd4", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "6bc1bee22e409f96e93d7e117393172a", + "bd334f1d6e45f25ff712a214571fa5cc", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "974104846d0ad3ad7734ecb3ecee4eef", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "30c81c46a35ce411e5fbc1191a0a52ef", + "ef7afd2270e2e60adce0ba2face6444e", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f69f2445df4f9b17ad2b417be66c3710", + "9a4b41ba738d6c72fb16691603c18e0e", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "6bc1bee22e409f96e93d7e117393172a", + "f3eed1bdb5d2a03c064b5a7e3db181f8", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "591ccb10d410ed26dc5ba74a31362870", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "30c81c46a35ce411e5fbc1191a0a52ef", + "b6ed21b99ca6f4f9f153e7b1beafed1d", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f69f2445df4f9b17ad2b417be66c3710", + "23304b7a39f9f3ff067d8d8f9e24ecc7", + }, + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + ctx: aes.Context_ECB + dst: [aes.BLOCK_SIZE]byte + aes.init_ecb(&ctx, key, impl) + + aes.encrypt_ecb(&ctx, dst[:], plaintext) + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.ciphertext, + fmt.tprintf( + "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.plaintext, + dst_str, + ), + ) + + aes.decrypt_ecb(&ctx, dst[:], ciphertext) + dst_str = string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.plaintext, + fmt.tprintf( + "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", + impl, + v.plaintext, + v.key, + v.ciphertext, + dst_str, + ), + ) + } +} + +@(test) +test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-CTR/%v", impl)) + + test_vectors := []struct { + key: string, + iv: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", + }, + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aes.Context_CTR + aes.init_ctr(&ctx, key, iv, impl) + + aes.xor_bytes_ctr(&ctx, dst, plaintext) + + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.ciphertext, + fmt.tprintf( + "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.iv, + v.plaintext, + dst_str, + ), + ) + } + + // Incrementally read 1, 2, 3, ..., 2048 bytes of keystream, and + // compare the SHA-512/256 digest with a known value. Results + // and testcase taken from a known good implementation. + + tmp := make([]byte, 2048, context.temp_allocator) + + ctx: aes.Context_CTR + key: [aes.KEY_SIZE_256]byte + nonce: [aes.CTR_IV_SIZE]byte + aes.init_ctr(&ctx, key[:], nonce[:]) + + h_ctx: sha2.Context_512 + sha2.init_512_256(&h_ctx) + + for i := 1; i < 2048; i = i + 1 { + aes.keystream_bytes_ctr(&ctx, tmp[:i]) + sha2.update(&h_ctx, tmp[:i]) + } + + digest: [32]byte + sha2.final(&h_ctx, digest[:]) + digest_str := string(hex.encode(digest[:], context.temp_allocator)) + + expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" + tc.expect( + t, + expected_digest_str == digest_str, + fmt.tprintf( + "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + digest_str, + ), + ) +} + +@(test) +test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-GCM/%v", impl)) + + // NIST did a reorg of their site, so the source of the test vectors + // is only available from an archive. The commented out tests are + // for non-96-bit IVs which our implementation does not support. + // + // https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf + test_vectors := []struct { + key: string, + iv: string, + aad: string, + plaintext: string, + ciphertext: string, + tag: string, + } { + { + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "58e2fccefa7e3061367f1d57a4e7455a", + }, + { + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "0388dace60b6a392f328c2b971b2fe78", + "ab6e47d42cec13bdf53a67b21257bddf", + }, + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985", + "4d5c2af327cd64a62cf35abd2ba6fab4", + }, + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", + "5bc94fbc3221a5db94fae95ae7121a47", + }, + /* + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598", + "3612d2e79e3b0785561be14aaca2fccb", + }, + { + "feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5", + "619cc5aefffe0bfa462af43c1699d050", + }, + */ + { + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "cd33b28ac773f74ba00ed1f312572435", + }, + { + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "98e7247c07f0fe411c267e4384b0f600", + "2ff58d80033927ab8ef4d4587514f0fb", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256", + "9924a7c8587336bfb118024db8674a14", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710", + "2519498e80f1478f37ba55bd6d27618c", + }, + /* + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7", + "65dcc57fcf623a24094fcca40d3533f8", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b", + "dcf566ff291c25bbb8568fc3d376a6d9", + }, + */ + { + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "530f8afbc74536b9a963b4f1c4cb738b", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "cea7403d4d606b6e074ec5d3baf39d18", + "d0d1c8a799996bf0265b98b5d48ab919", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad", + "b094dac5d93471bdec1a502270e3cc6c", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", + "76fc6ece0f4e1768cddf8853bb2d551b", + }, + /* + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f", + "3a337dbf46a792c45e454913fe2ea8f2", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f", + "a44a8266ee1c8eb0c8b5d4cf5ae9f19a", + }, + */ + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) + aad, _ := hex.decode(transmute([]byte)(v.aad), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + tag, _ := hex.decode(transmute([]byte)(v.tag), context.temp_allocator) + + tag_ := make([]byte, len(tag), context.temp_allocator) + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key, impl) + + aes.seal_gcm(&ctx, dst, tag_, iv, aad, plaintext) + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tag_str := string(hex.encode(tag_[:], context.temp_allocator)) + + tc.expect( + t, + dst_str == v.ciphertext && tag_str == v.tag, + fmt.tprintf( + "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, + ), + ) + + ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag) + dst_str = string(hex.encode(dst[:], context.temp_allocator)) + + tc.expect( + t, + ok && dst_str == v.plaintext, + fmt.tprintf( + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, + ), + ) + } +} diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin index cc69cb16d..600dc9ade 100644 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ b/tests/core/crypto/test_crypto_benchmark.odin @@ -6,6 +6,7 @@ import "core:fmt" import "core:testing" import "core:time" +import "core:crypto/aes" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" import "core:crypto/ed25519" @@ -25,6 +26,7 @@ bench_crypto :: proc(t: ^testing.T) { bench_chacha20(t) bench_poly1305(t) bench_chacha20poly1305(t) + bench_aes256_gcm(t) bench_ed25519(t) bench_x25519(t) } @@ -134,6 +136,26 @@ _benchmark_chacha20poly1305 :: proc( return nil } +_benchmark_aes256_gcm :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + nonce: [aes.GCM_NONCE_SIZE]byte + tag: [aes.GCM_TAG_SIZE]byte = --- + + ctx := transmute(^aes.Context_GCM)context.user_ptr + + for _ in 0 ..= options.rounds { + aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { fmt.printf( "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", @@ -221,6 +243,44 @@ bench_chacha20poly1305 :: proc(t: ^testing.T) { benchmark_print(name, options) } +bench_aes256_gcm :: proc(t: ^testing.T) { + name := "AES256-GCM 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_gcm, + teardown = _teardown_sized_buf, + } + + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key[:]) + + context.user_ptr = &ctx + + err := time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) + + name = "AES256-GCM 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) + + name = "AES256-GCM 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) +} + bench_ed25519 :: proc(t: ^testing.T) { iters :: 10000 From 8ae375dbffa344dc6f99fe0dc82f9070559ab137 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:23:54 -0400 Subject: [PATCH 012/151] Move log timestamping out to `do_time_header` proc --- core/log/file_console_logger.odin | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index bcce67578..99ca1f51a 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -73,15 +73,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string do_level_header(options, level, &buf) when time.IS_SUPPORTED { - if Full_Timestamp_Opts & options != nil { - fmt.sbprint(&buf, "[") - t := time.now() - y, m, d := time.date(t) - h, min, s := time.clock(t) - if .Date in options { fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d) } - if .Time in options { fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s) } - fmt.sbprint(&buf, "] ") - } + do_time_header(options, &buf, time.now()) } do_location_header(options, &buf, location) @@ -125,6 +117,19 @@ do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { } } +do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { + when time.IS_SUPPORTED { + if Full_Timestamp_Opts & opts != nil { + fmt.sbprint(buf, "[") + y, m, d := time.date(t) + h, min, s := time.clock(t) + if .Date in opts { fmt.sbprintf(buf, "%d-%02d-%02d ", y, m, d) } + if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } + fmt.sbprint(buf, "] ") + } + } +} + do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) { if Location_Header_Opts & opts == nil { return From 7d4da6eaa7cf420b1808652cb44763e259634001 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:29:18 -0400 Subject: [PATCH 013/151] Fix trailing space with only `.Date` log option --- core/log/file_console_logger.odin | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 99ca1f51a..6843d3a63 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -123,7 +123,12 @@ do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { fmt.sbprint(buf, "[") y, m, d := time.date(t) h, min, s := time.clock(t) - if .Date in opts { fmt.sbprintf(buf, "%d-%02d-%02d ", y, m, d) } + if .Date in opts { + fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d) + if .Time in opts { + fmt.sbprint(buf, " ") + } + } if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } fmt.sbprint(buf, "] ") } From 1875e7c36a4aaee71aa39defe0d169104a8030b0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 15:56:58 -0400 Subject: [PATCH 014/151] Make `log.do_*_header` argument orders consistent --- core/log/file_console_logger.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 6843d3a63..661f5d408 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -70,7 +70,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) - do_level_header(options, level, &buf) + do_level_header(options, &buf, level) when time.IS_SUPPORTED { do_time_header(options, &buf, time.now()) @@ -91,7 +91,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } -do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { +do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { RESET :: "\x1b[0m" RED :: "\x1b[31m" From 558c330028e90db9934d82414e7b21374ed58e1c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:43:26 -0400 Subject: [PATCH 015/151] Add task-stopping functionality to `thread.Pool` --- core/thread/thread_pool.odin | 130 +++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 15 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index fddcac89e..53f89ea0c 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -44,6 +44,29 @@ Pool :: struct { tasks_done: [dynamic]Task, } +Pool_Thread_Data :: struct { + pool: ^Pool, + task: Task, +} + +@(private="file") +pool_thread_runner :: proc(t: ^Thread) { + data := cast(^Pool_Thread_Data)t.data + pool := data.pool + + for intrinsics.atomic_load(&pool.is_running) { + sync.wait(&pool.sem_available) + + if task, ok := pool_pop_waiting(pool); ok { + data.task = task + pool_do_work(pool, task) + data.task = {} + } + } + + sync.post(&pool.sem_available, 1) +} + // Once initialized, the pool's memory address is not allowed to change until // it is destroyed. // @@ -58,21 +81,11 @@ pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { pool.is_running = true for _, i in pool.threads { - t := create(proc(t: ^Thread) { - pool := (^Pool)(t.data) - - for intrinsics.atomic_load(&pool.is_running) { - sync.wait(&pool.sem_available) - - if task, ok := pool_pop_waiting(pool); ok { - pool_do_work(pool, task) - } - } - - sync.post(&pool.sem_available, 1) - }) + t := create(pool_thread_runner) + data := new(Pool_Thread_Data) + data.pool = pool t.user_index = i - t.data = pool + t.data = data pool.threads[i] = t } } @@ -82,6 +95,8 @@ pool_destroy :: proc(pool: ^Pool) { delete(pool.tasks_done) for &t in pool.threads { + data := cast(^Pool_Thread_Data)t.data + free(data, pool.allocator) destroy(t) } @@ -103,7 +118,7 @@ pool_join :: proc(pool: ^Pool) { yield() -started_count: int + started_count: int for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { @@ -138,6 +153,91 @@ pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Pro sync.post(&pool.sem_available, 1) } +// Forcibly stop a running task by its user index. +// +// This will terminate the underlying thread. Ideally, you should use some +// means of communication to stop a task, as thread termination may leave +// resources unclaimed. +// +// The thread will be restarted to accept new tasks. +// +// Returns true if the task was found and terminated. +pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.user_index == user_index && data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + pool.threads[i] = replacement + + start(replacement) + return true + } + } + + return false +} + +// Forcibly stop all running tasks. +// +// The same notes from `pool_stop_task` apply here. +pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + pool.threads[i] = replacement + + start(replacement) + } + } +} + +// Force the pool to stop all of its threads and put it into a state where +// it will no longer run any more tasks. +// +// The pool must still be destroyed after this. +pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) { + sync.guard(&pool.mutex) + + for t in pool.threads { + terminate(t, exit_code) + + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + } + } +} + // Number of tasks waiting to be processed. Only informational, mostly for // debugging. Don't rely on this value being consistent with other num_* // values. From 8137b9dd753672d5359828a3170545d856936f10 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:50:21 -0400 Subject: [PATCH 016/151] Add `mem.tracking_allocator_reset` --- core/mem/tracking_allocator.odin | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index bc624617d..1b57e5fb4 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -47,6 +47,7 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { } +// Clear only the current allocation data while keeping the totals intact. tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -55,6 +56,19 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } +// Reset all of a Tracking Allocator's allocation data back to zero. +tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { + sync.mutex_lock(&t.mutex) + clear(&t.allocation_map) + clear(&t.bad_free_array) + t.total_memory_allocated = 0 + t.total_allocation_count = 0 + t.total_memory_freed = 0 + t.total_free_count = 0 + t.peak_memory_allocated = 0 + t.current_memory_allocated = 0 + sync.mutex_unlock(&t.mutex) +} @(require_results) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { From fc4f6b87bb777d73b506a16749e934e076a5b8bc Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 15:51:28 -0400 Subject: [PATCH 017/151] Add `core:encoding/ansi` package --- core/encoding/ansi/ansi.odin | 137 ++++++++++++++++++++++++++++++ core/encoding/ansi/doc.odin | 20 +++++ core/log/file_console_logger.odin | 9 +- 3 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 core/encoding/ansi/ansi.odin create mode 100644 core/encoding/ansi/doc.odin diff --git a/core/encoding/ansi/ansi.odin b/core/encoding/ansi/ansi.odin new file mode 100644 index 000000000..5550a1671 --- /dev/null +++ b/core/encoding/ansi/ansi.odin @@ -0,0 +1,137 @@ +package ansi + +BEL :: "\a" // Bell +BS :: "\b" // Backspace +ESC :: "\e" // Escape + +// Fe Escape sequences + +CSI :: ESC + "[" // Control Sequence Introducer +OSC :: ESC + "]" // Operating System Command +ST :: ESC + "\\" // String Terminator + +// CSI sequences + +CUU :: "A" // Cursor Up +CUD :: "B" // Cursor Down +CUF :: "C" // Cursor Forward +CUB :: "D" // Cursor Back +CNL :: "E" // Cursor Next Line +CPL :: "F" // Cursor Previous Line +CHA :: "G" // Cursor Horizontal Absolute +CUP :: "H" // Cursor Position +ED :: "J" // Erase in Display +EL :: "K" // Erase in Line +SU :: "S" // Scroll Up +SD :: "T" // Scroll Down +HVP :: "f" // Horizontal Vertical Position +SGR :: "m" // Select Graphic Rendition +AUX_ON :: "5i" // AUX Port On +AUX_OFF :: "4i" // AUX Port Off +DSR :: "6n" // Device Status Report + +// CSI: private sequences + +SCP :: "s" // Save Current Cursor Position +RCP :: "u" // Restore Saved Cursor Position +DECAWM_ON :: "?7h" // Auto Wrap Mode (Enabled) +DECAWM_OFF :: "?7l" // Auto Wrap Mode (Disabled) +DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible) +DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible) + +// SGR sequences + +RESET :: "0" +BOLD :: "1" +FAINT :: "2" +ITALIC :: "3" // Not widely supported. +UNDERLINE :: "4" +BLINK_SLOW :: "5" +BLINK_RAPID :: "6" // Not widely supported. +INVERT :: "7" // Also known as reverse video. +HIDE :: "8" // Not widely supported. +STRIKE :: "9" +FONT_PRIMARY :: "10" +FONT_ALT1 :: "11" +FONT_ALT2 :: "12" +FONT_ALT3 :: "13" +FONT_ALT4 :: "14" +FONT_ALT5 :: "15" +FONT_ALT6 :: "16" +FONT_ALT7 :: "17" +FONT_ALT8 :: "18" +FONT_ALT9 :: "19" +FONT_FRAKTUR :: "20" // Rarely supported. +UNDERLINE_DOUBLE :: "21" // May be interpreted as "disable bold." +NO_BOLD_FAINT :: "22" +NO_ITALIC_BLACKLETTER :: "23" +NO_UNDERLINE :: "24" +NO_BLINK :: "25" +PROPORTIONAL_SPACING :: "26" +NO_REVERSE :: "27" +NO_HIDE :: "28" +NO_STRIKE :: "29" + +FG_BLACK :: "30" +FG_RED :: "31" +FG_GREEN :: "32" +FG_YELLOW :: "33" +FG_BLUE :: "34" +FG_MAGENTA :: "35" +FG_CYAN :: "36" +FG_WHITE :: "37" +FG_COLOR :: "38" +FG_COLOR_8_BIT :: "38;5" // Followed by ";n" where n is in 0..=255 +FG_COLOR_24_BIT :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +FG_DEFAULT :: "39" + +BG_BLACK :: "40" +BG_RED :: "41" +BG_GREEN :: "42" +BG_YELLOW :: "43" +BG_BLUE :: "44" +BG_MAGENTA :: "45" +BG_CYAN :: "46" +BG_WHITE :: "47" +BG_COLOR :: "48" +BG_COLOR_8_BIT :: "48;5" // Followed by ";n" where n is in 0..=255 +BG_COLOR_24_BIT :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +BG_DEFAULT :: "49" + +NO_PROPORTIONAL_SPACING :: "50" +FRAMED :: "51" +ENCIRCLED :: "52" +OVERLINED :: "53" +NO_FRAME_ENCIRCLE :: "54" +NO_OVERLINE :: "55" + +// SGR: non-standard bright colors + +FG_BRIGHT_BLACK :: "90" // Also known as grey. +FG_BRIGHT_RED :: "91" +FG_BRIGHT_GREEN :: "92" +FG_BRIGHT_YELLOW :: "93" +FG_BRIGHT_BLUE :: "94" +FG_BRIGHT_MAGENTA :: "95" +FG_BRIGHT_CYAN :: "96" +FG_BRIGHT_WHITE :: "97" + +BG_BRIGHT_BLACK :: "100" // Also known as grey. +BG_BRIGHT_RED :: "101" +BG_BRIGHT_GREEN :: "102" +BG_BRIGHT_YELLOW :: "103" +BG_BRIGHT_BLUE :: "104" +BG_BRIGHT_MAGENTA :: "105" +BG_BRIGHT_CYAN :: "106" +BG_BRIGHT_WHITE :: "107" + +// Fp Escape sequences + +DECSC :: ESC + "7" // DEC Save Cursor +DECRC :: ESC + "8" // DEC Restore Cursor + +// OSC sequences + +WINDOW_TITLE :: "2" // Followed by ";" ST. +HYPERLINK :: "8" // Followed by ";[params];" ST. Closed by OSC HYPERLINK ";;" ST. +CLIPBOARD :: "52" // Followed by ";c;" ST. diff --git a/core/encoding/ansi/doc.odin b/core/encoding/ansi/doc.odin new file mode 100644 index 000000000..a0945c581 --- /dev/null +++ b/core/encoding/ansi/doc.odin @@ -0,0 +1,20 @@ +/* +package ansi implements constant references to many widely-supported ANSI +escape codes, primarily used in terminal emulators for enhanced graphics, such +as colors, text styling, and animated displays. + +For example, you can print out a line of cyan text like this: + fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons: + fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +If your terminal supports 24-bit true color mode, you can also do this: + fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +For more information, see: + 1. https://en.wikipedia.org/wiki/ANSI_escape_code + 2. https://www.vt100.net/docs/vt102-ug/chapter5.html + 3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +*/ +package ansi diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 661f5d408..fb968ccb6 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,6 +1,7 @@ //+build !freestanding package log +import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" @@ -93,10 +94,10 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { - RESET :: "\x1b[0m" - RED :: "\x1b[31m" - YELLOW :: "\x1b[33m" - DARK_GREY :: "\x1b[90m" + RESET :: ansi.CSI + ansi.RESET + ansi.SGR + RED :: ansi.CSI + ansi.FG_RED + ansi.SGR + YELLOW :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR + DARK_GREY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR col := RESET switch level { From 50dffaf131452f64a2b28477718850ad2a8b78f2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 16:29:34 -0400 Subject: [PATCH 018/151] Add `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 319 +++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 core/mem/rollback_stack_allocator.odin diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin new file mode 100644 index 000000000..bf397d2c8 --- /dev/null +++ b/core/mem/rollback_stack_allocator.odin @@ -0,0 +1,319 @@ +package mem + +// The Rollback Stack Allocator was designed for the test runner to be fast, +// able to grow, and respect the Tracking Allocator's requirement for +// individual frees. It is not overly concerned with fragmentation, however. +// +// It has support for expansion when configured with a block allocator and +// limited support for out-of-order frees. +// +// Allocation has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks. +// +// Allocation follows a first-fit strategy when there are multiple memory +// blocks. +// +// Freeing has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks and number +// of freed items preceding the last item in a block. +// +// Resizing has constant-time performance, if it's the last item in a block, or +// the new size is smaller. Naturally, this becomes linear-time if there are +// multiple blocks to search for the pointer's owning block. Otherwise, the +// allocator defaults to a combined alloc & free operation internally. +// +// Out-of-order freeing is accomplished by collapsing a run of freed items +// from the last allocation backwards. +// +// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy +// the requested alignment. + +import "base:runtime" + +ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte + +// This limitation is due to the size of `prev_ptr`, but it is only for the +// head block; any allocation in excess of the allocator's `block_size` is +// valid, so long as the block allocator can handle it. +// +// This is because allocations over the block size are not split up if the item +// within is freed; they are immediately returned to the block allocator. +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 1 * Gigabyte + + +Rollback_Stack_Header :: bit_field u64 { + prev_offset: int | 32, + is_free: bool | 1, + prev_ptr: int | 31, +} + +Rollback_Stack_Block :: struct { + next_block: ^Rollback_Stack_Block, + last_alloc: rawptr, + offset: int, + buffer: []byte, +} + +Rollback_Stack :: struct { + head: ^Rollback_Stack_Block, + block_size: int, + block_allocator: Allocator, +} + + +@(private="file") +@(require_results) +rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { + start := cast(uintptr)raw_data(block.buffer) + end := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + return start < cast(uintptr)ptr && cast(uintptr)ptr <= end +} + +@(private="file") +@(require_results) +rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + parent: ^Rollback_Stack_Block, + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + err: Allocator_Error, +) { + for block = stack.head; block != nil; block = block.next_block { + if rb_ptr_in_bounds(block, ptr) { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return + } + parent = block + } + return nil, nil, nil, .Invalid_Pointer +} + +@(private="file") +@(require_results) +rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + ok: bool, +) { + for block = stack.head; block != nil; block = block.next_block { + if block.last_alloc == ptr { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return block, header, true + } + } + return nil, nil, false +} + +@(private="file") +rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { + header := header + for block.offset > 0 && header.is_free { + block.offset = header.prev_offset + block.last_alloc = cast(rawptr)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr) + header = cast(^Rollback_Stack_Header)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr - size_of(Rollback_Stack_Header)) + } +} + +@(private="file") +@(require_results) +rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { + parent, block, header := rb_find_ptr(stack, ptr) or_return + if header.is_free { + return .Invalid_Pointer + } + header.is_free = true + if block.last_alloc == ptr { + block.offset = header.prev_offset + rb_rollback_block(block, header) + } + if parent != nil && block.offset == 0 { + parent.next_block = block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + } + return nil +} + +@(private="file") +rb_free_all :: proc(stack: ^Rollback_Stack) { + for block := stack.head.next_block; block != nil; /**/ { + next_block := block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + block = next_block + } + + stack.head.next_block = nil + stack.head.last_alloc = nil + stack.head.offset = 0 +} + +@(private="file") +@(require_results) +rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + if ptr != nil { + if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + if block.offset + (size - old_size) < len(block.buffer) { + block.offset += (size - old_size) + #no_bounds_check return (cast([^]byte)ptr)[:size], nil + } + } + } + + result = rb_alloc(stack, size, alignment) or_return + runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) + err = rb_free(stack, ptr) + + return +} + +@(private="file") +@(require_results) +rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + parent: ^Rollback_Stack_Block + for block := stack.head; /**/; block = block.next_block { + if block == nil { + if stack.block_allocator.procedure == nil { + return nil, .Out_Of_Memory + } + + minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1 + new_block_size := max(minimum_size_required, stack.block_size) + block = rb_make_block(new_block_size, stack.block_allocator) or_return + parent.next_block = block + } + + start := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + padding := calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + + if block.offset + padding + size > len(block.buffer) { + parent = block + continue + } + + header := cast(^Rollback_Stack_Header)(start + cast(uintptr)padding - size_of(Rollback_Stack_Header)) + ptr := (cast([^]byte)(start + cast(uintptr)padding)) + + header^ = { + prev_offset = block.offset, + prev_ptr = max(0, cast(int)(cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer))), + is_free = false, + } + + block.last_alloc = ptr + block.offset += padding + size + + if len(block.buffer) > stack.block_size { + // This block exceeds the allocator's standard block size and is considered a singleton. + // Prevent any further allocations on it. + block.offset = len(block.buffer) + } + + #no_bounds_check return ptr[:size], nil + } + + return nil, .Out_Of_Memory +} + +@(private="file") +@(require_results) +rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { + buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return + + block = cast(^Rollback_Stack_Block)raw_data(buffer) + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + return +} + + +rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte) { + MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) + assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.") + + block := cast(^Rollback_Stack_Block)raw_data(buffer) + block^ = {} + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + + stack^ = {} + stack.head = block + stack.block_size = len(block.buffer) +} + +rollback_stack_init_dynamic :: proc( + stack: ^Rollback_Stack, + block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, + block_allocator := context.allocator, +) -> Allocator_Error { + assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 1 gigabyte.") + + block := rb_make_block(block_size, block_allocator) or_return + + stack^ = {} + stack.head = block + stack.block_size = block_size + stack.block_allocator = block_allocator + + return nil +} + +rollback_stack_init :: proc { + rollback_stack_init_buffered, + rollback_stack_init_dynamic, +} + +rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { + if stack.block_allocator.procedure != nil { + rb_free_all(stack) + free(stack.head, stack.block_allocator) + } + stack^ = {} +} + +@(require_results) +rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { + return Allocator { + data = stack, + procedure = rollback_stack_allocator_proc, + } +} + +@(require_results) +rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, location := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + stack := cast(^Rollback_Stack)allocator_data + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + assert(is_power_of_two(cast(uintptr)alignment), "alignment must be a power of two", location) + result = rb_alloc(stack, size, alignment) or_return + + if mode == .Alloc { + zero_slice(result) + } + + case .Free: + err = rb_free(stack, old_memory) + + case .Free_All: + rb_free_all(stack) + + case .Resize, .Resize_Non_Zeroed: + result = rb_resize(stack, old_memory, old_size, size, alignment) or_return + + #no_bounds_check if mode == .Resize && size > old_size { + zero_slice(result[old_size:]) + } + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return +} From 95c2e020fff74f8e2e193db595c25726a8b9a99e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 19:59:49 -0400 Subject: [PATCH 019/151] Share `libc` signal definitions with more platforms I confirmed that these 3 platforms share the same signal definitions from these sources. Haiku: https://github.com/haiku/haiku/blob/master/headers/posix/signal.h OpenBSD: https://github.com/openbsd/src/blob/master/sys/sys/signal.h NetBSD: http://fxr.watson.org/fxr/source/sys/signal.h?v=NETBSD --- core/c/libc/signal.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/c/libc/signal.odin b/core/c/libc/signal.odin index 186b74d8c..1489779fe 100644 --- a/core/c/libc/signal.odin +++ b/core/c/libc/signal.odin @@ -34,7 +34,7 @@ when ODIN_OS == .Windows { SIGTERM :: 15 } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SIG_ERR :: rawptr(~uintptr(0)) SIG_DFL :: rawptr(uintptr(0)) SIG_IGN :: rawptr(uintptr(1)) From b6c4dfb68d5d7d2ac883eaa18409eb8e7d6f2e9c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 19:44:19 -0400 Subject: [PATCH 020/151] Refactor the test runner Changes - Support multi-threaded testing. - Support `set_fail_timeout` on all platforms. - Display an animated progress report. - Setup all tests with a context logger. - Give all tests their own separate custom allocators. - Support tracking test memory usage. - Display a summary of the failed tests at the end. - Let users select only specific tests to run. - Support copying failed tests to the clipboard to run again. - Support catching SIGINT (CTRL-C) to cancel early. - Record context in cleanup procs. - Write all log messages to STDERR for easy redirection. - Possibly more I've forgotten. New Options - `-define:test_threads=N`: Specify thread count. - `-define:test_thread_memory=B`: Specify initial memory block size in bytes to each thread. - `-define:test_track_memory=true`: Track the memory usage of individual tests. - `-define:test_fancy=false`: Disable animated progress report. - `-define:test_select=package.test_name,...`: Run only select tests. - `-define:test_clipboard=true`: Copy names of failed tests to the clipboard. - `-define:test_progress_width=24`: Change the width of the animated progress bars. --- core/testing/events.odin | 48 ++ core/testing/logging.odin | 71 +++ core/testing/reporting.odin | 312 +++++++++++ core/testing/runner.odin | 692 +++++++++++++++++++++++-- core/testing/signal_handler.odin | 19 + core/testing/signal_handler_other.odin | 11 + core/testing/testing.odin | 59 ++- 7 files changed, 1141 insertions(+), 71 deletions(-) create mode 100644 core/testing/events.odin create mode 100644 core/testing/logging.odin create mode 100644 core/testing/reporting.odin create mode 100644 core/testing/signal_handler.odin create mode 100644 core/testing/signal_handler_other.odin diff --git a/core/testing/events.odin b/core/testing/events.odin new file mode 100644 index 000000000..bab35aaad --- /dev/null +++ b/core/testing/events.odin @@ -0,0 +1,48 @@ +//+private +package testing + +import "base:runtime" +import "core:sync/chan" +import "core:time" + +Test_State :: enum { + Ready, + Running, + Successful, + Failed, +} + +Update_Channel :: chan.Chan(Channel_Event) +Update_Channel_Sender :: chan.Chan(Channel_Event, .Send) + +Task_Channel :: struct { + channel: Update_Channel, + test_index: int, +} + +Event_New_Test :: struct { + test_index: int, +} + +Event_State_Change :: struct { + new_state: Test_State, +} + +Event_Set_Fail_Timeout :: struct { + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +Event_Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + formatted_text: string, +} + +Channel_Event :: union { + Event_New_Test, + Event_State_Change, + Event_Set_Fail_Timeout, + Event_Log_Message, +} diff --git a/core/testing/logging.odin b/core/testing/logging.odin new file mode 100644 index 000000000..5bbbffeae --- /dev/null +++ b/core/testing/logging.odin @@ -0,0 +1,71 @@ +//+private +package testing + +import "base:runtime" +import "core:fmt" +import pkg_log "core:log" +import "core:strings" +import "core:sync/chan" +import "core:time" + +Default_Test_Logger_Opts :: runtime.Logger_Options { + .Level, + .Terminal_Color, + .Short_File_Path, + .Line, + .Procedure, + .Date, .Time, +} + +Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + // `text` may be allocated differently, depending on where a log message + // originates from. + allocator: runtime.Allocator, +} + +test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + t := cast(^T)logger_data + + if level >= .Error { + t.error_count += 1 + } + + cloned_text, clone_error := strings.clone(text, t._log_allocator) + assert(clone_error == nil, "Error while cloning string in test thread logger proc.") + + now := time.now() + + chan.send(t.channel, Event_Log_Message { + level = level, + text = cloned_text, + time = now, + formatted_text = format_log_text(level, text, options, location, now, t._log_allocator), + }) +} + +runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + log_messages := cast(^[dynamic]Log_Message)logger_data + + now := time.now() + + append(log_messages, Log_Message { + level = level, + text = format_log_text(level, text, options, location, now), + time = now, + allocator = context.allocator, + }) +} + +format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location: runtime.Source_Code_Location, at_time: time.Time, allocator := context.allocator) -> string{ + backing: [1024]byte + buf := strings.builder_from_bytes(backing[:]) + + pkg_log.do_level_header(options, &buf, level) + pkg_log.do_time_header(options, &buf, at_time) + pkg_log.do_location_header(options, &buf, location) + + return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator) +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin new file mode 100644 index 000000000..d06681c2d --- /dev/null +++ b/core/testing/reporting.odin @@ -0,0 +1,312 @@ +//+private +package testing + +import "base:runtime" +import "core:encoding/ansi" +import "core:fmt" +import "core:io" +import "core:mem" +import "core:path/filepath" +import "core:strings" + +// Definitions of colors for use in the test runner. +SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR +SGR_READY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR +SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR +SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN + ansi.SGR +SGR_FAILED :: ansi.CSI + ansi.FG_RED + ansi.SGR + +// More than enough bytes to cover long package names, long test names, dozens +// of ANSI codes, et cetera. +LINE_BUFFER_SIZE :: (PROGRESS_WIDTH * 8 + 256) * runtime.Byte + +PROGRESS_COLUMN_SPACING :: 2 + +Package_Run :: struct { + name: string, + header: string, + + frame_ready: bool, + + redraw_buffer: [LINE_BUFFER_SIZE]byte, + redraw_string: string, + + last_change_state: Test_State, + last_change_name: string, + + tests: []Internal_Test, + test_states: []Test_State, +} + +Report :: struct { + packages: []Package_Run, + packages_by_name: map[string]^Package_Run, + + pkg_column_len: int, + test_column_len: int, + + all_tests: []Internal_Test, + all_test_states: []Test_State, +} + +// Organize all tests by package and sort out test state data. +make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error) { + assert(len(internal_tests) > 0, "make_report called with no tests") + + packages: [dynamic]Package_Run + + report.all_tests = internal_tests + report.all_test_states = make([]Test_State, len(internal_tests)) or_return + + // First, figure out what belongs where. + #no_bounds_check cur_pkg := internal_tests[0].pkg + pkg_start: int + + // This loop assumes the tests are sorted by package already. + for it, index in internal_tests { + if cur_pkg != it.pkg { + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + tests = report.all_tests[pkg_start:index], + test_states = report.all_test_states[pkg_start:index], + }) or_return + } + + pkg_start = index + report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg)) + cur_pkg = it.pkg + } + report.test_column_len = max(report.test_column_len, len(it.name)) + } + + // Handle the last package. + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + header = cur_pkg, + tests = report.all_tests[pkg_start:], + test_states = report.all_test_states[pkg_start:], + }) or_return + } + + report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg)) + + shrink(&packages) or_return + + for &pkg in packages { + pkg.header = fmt.aprintf("%- *[1]s[", pkg.name, report.pkg_column_len) + assert(len(pkg.header) > 0, "Error allocating package header string.") + + // This is safe because the array is done resizing, and it has the same + // lifetime as the map. + report.packages_by_name[pkg.name] = &pkg + } + + // It's okay to discard the dynamic array's allocator information here, + // because its capacity has been shrunk to its length, it was allocated by + // the caller's context allocator, and it will be deallocated by the same. + // + // `delete_slice` is equivalent to `delete_dynamic_array` in this case. + report.packages = packages[:] + + return +} + +destroy_report :: proc(report: ^Report) { + for pkg in report.packages { + delete(pkg.header) + } + + delete(report.packages) + delete(report.packages_by_name) + delete(report.all_test_states) +} + +redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { + if pkg.frame_ready { + io.write_string(w, pkg.redraw_string) + return + } + + // Write the output line here so we can cache it. + line_builder := strings.builder_from_bytes(pkg.redraw_buffer[:]) + line_writer := strings.to_writer(&line_builder) + + highest_run_index: int + failed_count: int + done_count: int + #no_bounds_check for i := 0; i < len(pkg.test_states); i += 1 { + switch pkg.test_states[i] { + case .Ready: + continue + case .Running: + highest_run_index = max(highest_run_index, i) + case .Successful: + done_count += 1 + case .Failed: + failed_count += 1 + done_count += 1 + } + } + + start := max(0, highest_run_index - (PROGRESS_WIDTH - 1)) + end := min(start + PROGRESS_WIDTH, len(pkg.test_states)) + + // This variable is to keep track of the last ANSI code emitted, in + // order to avoid repeating the same code over in a sequence. + // + // This should help reduce screen flicker. + last_state := Test_State(-1) + + io.write_string(line_writer, pkg.header) + + #no_bounds_check for state in pkg.test_states[start:end] { + switch state { + case .Ready: + if last_state != state { + io.write_string(line_writer, SGR_READY) + last_state = state + } + case .Running: + if last_state != state { + io.write_string(line_writer, SGR_RUNNING) + last_state = state + } + case .Successful: + if last_state != state { + io.write_string(line_writer, SGR_SUCCESS) + last_state = state + } + case .Failed: + if last_state != state { + io.write_string(line_writer, SGR_FAILED) + last_state = state + } + } + io.write_byte(line_writer, '|') + } + + for _ in 0 ..< PROGRESS_WIDTH - (end - start) { + io.write_byte(line_writer, ' ') + } + + io.write_string(line_writer, SGR_RESET + "] ") + + ticker: string + if done_count == len(pkg.test_states) { + ticker = "[package done]" + if failed_count > 0 { + ticker = fmt.tprintf("%s (" + SGR_FAILED + "%i" + SGR_RESET + " failed)", ticker, failed_count) + } + } else { + if len(pkg.last_change_name) == 0 { + #no_bounds_check pkg.last_change_name = pkg.tests[0].name + } + + switch pkg.last_change_state { + case .Ready: + ticker = fmt.tprintf(SGR_READY + "%s" + SGR_RESET, pkg.last_change_name) + case .Running: + ticker = fmt.tprintf(SGR_RUNNING + "%s" + SGR_RESET, pkg.last_change_name) + case .Failed: + ticker = fmt.tprintf(SGR_FAILED + "%s" + SGR_RESET, pkg.last_change_name) + case .Successful: + ticker = fmt.tprintf(SGR_SUCCESS + "%s" + SGR_RESET, pkg.last_change_name) + } + } + + if done_count == len(pkg.test_states) { + fmt.wprintfln(line_writer, " % 4i :: %s", + len(pkg.test_states), + ticker, + ) + } else { + fmt.wprintfln(line_writer, "% 4i/% 4i :: %s", + done_count, + len(pkg.test_states), + ticker, + ) + } + + pkg.redraw_string = strings.to_string(line_builder) + pkg.frame_ready = true + io.write_string(w, pkg.redraw_string) +} + +redraw_report :: proc(w: io.Writer, report: Report) { + // If we print a line longer than the user's terminal can handle, it may + // wrap around, shifting the progress report out of alignment. + // + // There are ways to get the current terminal width, and that would be the + // ideal way to handle this, but it would require system-specific code such + // as setting STDIN to be non-blocking in order to read the response from + // the ANSI DSR escape code, or reading environment variables. + // + // The DECAWM escape codes control whether or not the terminal will wrap + // long lines or overwrite the last visible character. + // This should be fine for now. + // + // Note that we only do this for the animated summary; log messages are + // still perfectly fine to wrap, as they're printed in their own batch, + // whereas the animation depends on each package being only on one line. + // + // Of course, if you resize your terminal while it's printing, things can + // still break... + fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF) + for &pkg in report.packages { + redraw_package(w, &pkg) + } + fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON) +} + +needs_to_redraw :: proc(report: Report) -> bool { + for pkg in report.packages { + if !pkg.frame_ready { + return true + } + } + + return false +} + +draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) { + if total_done_count != total_test_count { + fmt.wprintfln(w, + "%s % 4i/% 4i :: total", + threads_string, + total_done_count, + total_test_count) + } +} + +write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg, name: string) { + fmt.wprintf(w, + "<% 10M/% 10M> <% 10M> (% 5i/% 5i) :: %s.%s", + tracker.current_memory_allocated, + tracker.total_memory_allocated, + tracker.peak_memory_allocated, + tracker.total_free_count, + tracker.total_allocation_count, + pkg, + name) + + for ptr, entry in tracker.allocation_map { + fmt.wprintf(w, + "\n +++ leak % 10M @ %p [%s:%i:%s()]", + entry.size, + ptr, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } + + for entry in tracker.bad_free_array { + fmt.wprintf(w, + "\n +++ bad free @ %p [%s:%i:%s()]", + entry.memory, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } +} diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 0039f1939..ce5aa112a 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,73 +1,675 @@ //+private package testing +import "base:intrinsics" +import "base:runtime" +import "core:bytes" +import "core:encoding/ansi" +import "core:encoding/base64" +import "core:fmt" import "core:io" +import pkg_log "core:log" +import "core:mem" import "core:os" import "core:slice" +import "core:strings" +import "core:sync/chan" +import "core:thread" +import "core:time" + +// Keep `-vet` happy. +base64_encode :: base64.encode +_ :: pkg_log +_ :: strings + +// Specify how many threads to use when running tests. +TEST_THREADS : int : #config(test_threads, 0) +// Track the memory used by each test. +TRACKING_MEMORY : bool : #config(test_track_memory, false) +// Specify how much memory each thread allocator starts with. +PER_THREAD_MEMORY : int : #config(test_thread_memory, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +// Select a specific set of tests to run by name. +TEST_SELECT : string : #config(test_select, "") +// Show the fancy animated progress report. +FANCY_OUTPUT : bool : #config(test_fancy, true) +// Copy failed tests to the clipboard when done. +USE_CLIPBOARD : bool : #config(test_clipboard, false) +// How many test results to show at a time per package. +PROGRESS_WIDTH : int : #config(test_progress_width, 24) + -reset_t :: proc(t: ^T) { - clear(&t.cleanups) - t.error_count = 0 -} end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { - c := t.cleanups[i] + #no_bounds_check c := t.cleanups[i] + context = c.ctx c.procedure(c.user_data) } + + delete(t.cleanups) + t.cleanups = {} +} + +Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, +} + +Task_Timeout :: struct { + test_index: int, + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +run_test_task :: proc(task: thread.Task) { + data := cast(^Task_Data)(task.data) + + chan.send(data.t.channel, Event_New_Test { + test_index = task.user_index, + }) + + chan.send(data.t.channel, Event_State_Change { + new_state = .Running, + }) + + context.logger = { + procedure = test_logger_proc, + data = &data.t, + lowest_level = .Debug if ODIN_DEBUG else .Info, + options = Default_Test_Logger_Opts, + } + + free_all(context.temp_allocator) + + run_internal_test(&data.t, data.it) + + end_t(&data.t) + + new_state : Test_State = .Failed if failed(&data.t) else .Successful + + chan.send(data.t.channel, Event_State_Change { + new_state = new_state, + }) } runner :: proc(internal_tests: []Internal_Test) -> bool { - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) + BATCH_BUFFER_SIZE :: 32 * mem.Kilobyte + POOL_BLOCK_SIZE :: 16 * mem.Kilobyte + CLIPBOARD_BUFFER_SIZE :: 16 * mem.Kilobyte - t := &T{} - t.w = w - reserve(&t.cleanups, 1024) - defer delete(t.cleanups) + BUFFERED_EVENTS_PER_CHANNEL :: 16 + RESERVED_LOG_MESSAGES :: 64 + RESERVED_TEST_FAILURES :: 64 - total_success_count := 0 - total_test_count := len(internal_tests) + ERROR_STRING_TIMEOUT : string : "Test timed out." + ERROR_STRING_UNKNOWN : string : "Test failed for unknown reasons." + OSC_WINDOW_TITLE : string : ansi.OSC + ansi.WINDOW_TITLE + ";Odin test runner (%i/%i)" + ansi.ST - slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { - if a.pkg < b.pkg { - return true + safe_delete_string :: proc(s: string, allocator := context.allocator) { + // Guard against bad frees on static strings. + switch raw_data(s) { + case raw_data(ERROR_STRING_TIMEOUT), raw_data(ERROR_STRING_UNKNOWN): + return + case: + delete(s, allocator) } - return a.name < b.name - }) + } - prev_pkg := "" + stdout := io.to_writer(os.stream_from_handle(os.stdout)) + stderr := io.to_writer(os.stream_from_handle(os.stderr)) + + // -- Prepare test data. + + alloc_error: mem.Allocator_Error + + when TEST_SELECT != "" { + select_internal_tests: [dynamic]Internal_Test + defer delete(select_internal_tests) + + { + index_list := TEST_SELECT + for selector in strings.split_iterator(&index_list, ",") { + // Temp allocator is fine since we just need to identify which test it's referring to. + split_selector := strings.split(selector, ".", context.temp_allocator) + + found := false + switch len(split_selector) { + case 1: + // Only the test name? + #no_bounds_check name := split_selector[0] + find_test_by_name: for it in internal_tests { + if it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_name + } + } + case 2: + #no_bounds_check pkg := split_selector[0] + #no_bounds_check name := split_selector[1] + find_test_by_pkg_and_name: for it in internal_tests { + if it.pkg == pkg && it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_pkg_and_name + } + } + } + if !found { + fmt.wprintfln(stderr, "No test found for the name: %q", selector) + } + } + } + + // Intentional shadow with user-specified tests. + internal_tests := select_internal_tests[:] + } + + total_failure_count := 0 + total_success_count := 0 + total_done_count := 0 + total_test_count := len(internal_tests) + + when !FANCY_OUTPUT { + // This is strictly for updating the window title when the progress + // report is disabled. We're otherwise able to depend on the call to + // `needs_to_redraw`. + last_done_count := -1 + } + + if total_test_count == 0 { + // Exit early. + fmt.wprintln(stdout, "No tests to run.") + return true + } for it in internal_tests { - if it.p == nil { - total_test_count -= 1 - continue - } + // NOTE(Feoramund): The old test runner skipped over tests with nil + // procedures, but I couldn't find any case where they occurred. + // This assert stands to prevent any oversight on my part. + fmt.assertf(it.p != nil, "Test %s.%s has procedure.", it.pkg, it.name) + } - free_all(context.temp_allocator) - reset_t(t) - defer end_t(t) - - if prev_pkg != it.pkg { - prev_pkg = it.pkg - logf(t, "[Package: %s]", it.pkg) - } - - logf(t, "[Test: %s]", it.name) - - run_internal_test(t, it) - - if failed(t) { - logf(t, "[%s : FAILURE]", it.name) + slice.stable_sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { + if a.pkg == b.pkg { + return a.name < b.name } else { - logf(t, "[%s : SUCCESS]", it.name) - total_success_count += 1 + return a.pkg < b.pkg + } + }) + + // -- Set thread count. + + when TEST_THREADS == 0 { + thread_count := os.processor_core_count() + } else { + thread_count := max(1, TEST_THREADS) + } + + thread_count = min(thread_count, total_test_count) + + // -- Allocate. + + pool_stack: mem.Rollback_Stack + alloc_error = mem.rollback_stack_init(&pool_stack, POOL_BLOCK_SIZE) + fmt.assertf(alloc_error == nil, "Error allocating memory for thread pool: %v", alloc_error) + defer mem.rollback_stack_destroy(&pool_stack) + + pool: thread.Pool + thread.pool_init(&pool, mem.rollback_stack_allocator(&pool_stack), thread_count) + defer thread.pool_destroy(&pool) + + task_channels: []Task_Channel = --- + task_channels, alloc_error = make([]Task_Channel, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channels: %v", alloc_error) + defer delete(task_channels) + + for &task_channel, index in task_channels { + task_channel.channel, alloc_error = chan.create_buffered(Update_Channel, BUFFERED_EVENTS_PER_CHANNEL, context.allocator) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channel #%i: %v", index, alloc_error) + } + defer for &task_channel in task_channels { + chan.destroy(&task_channel.channel) + } + + // This buffer is used to batch writes to STDOUT or STDERR, to help reduce + // screen flickering. + batch_buffer: bytes.Buffer + bytes.buffer_init_allocator(&batch_buffer, 0, BATCH_BUFFER_SIZE) + batch_writer := io.to_writer(bytes.buffer_to_stream(&batch_buffer)) + defer bytes.buffer_destroy(&batch_buffer) + + report: Report = --- + report, alloc_error = make_report(internal_tests) + fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) + defer destroy_report(&report) + + when FANCY_OUTPUT { + // We cannot make use of the ANSI save/restore cursor codes, because they + // work by absolute screen coordinates. This will cause unnecessary + // scrollback if we print at the bottom of someone's terminal. + ansi_redraw_string := fmt.aprintf( + // ANSI for "go up N lines then erase the screen from the cursor forward." + ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + + // We'll combine this with the window title format string, since it + // can be printed at the same time. + "%s", + // 1 extra line for the status bar. + 1 + len(report.packages), OSC_WINDOW_TITLE) + assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") + defer delete(ansi_redraw_string) + + thread_count_status_string: string = --- + { + PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + + unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") + thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) + assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") + } + defer delete(thread_count_status_string) + } + + task_data_slots: []Task_Data = --- + task_data_slots, alloc_error = make([]Task_Data, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error) + defer delete(task_data_slots) + + safe_heap: mem.Mutex_Allocator + mem.mutex_allocator_init(&safe_heap, context.allocator) + safe_heap_allocator := mem.mutex_allocator(&safe_heap) + + // Tests rotate through these allocators as they finish. + task_allocators: []mem.Rollback_Stack = --- + task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocators: %v", alloc_error) + defer delete(task_allocators) + + when TRACKING_MEMORY { + task_memory_trackers: []mem.Tracking_Allocator = --- + task_memory_trackers, alloc_error = make([]mem.Tracking_Allocator, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for memory trackers: %v", alloc_error) + defer delete(task_memory_trackers) + } + + #no_bounds_check for i in 0 ..< thread_count { + alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY, block_allocator = safe_heap_allocator) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error) + when TRACKING_MEMORY { + mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i])) } } - logf(t, "----------------------------------------") - if total_test_count == 0 { - log(t, "NO TESTS RAN") - } else { - logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count) + + defer #no_bounds_check for i in 0 ..< thread_count { + when TRACKING_MEMORY { + mem.tracking_allocator_destroy(&task_memory_trackers[i]) + } + mem.rollback_stack_destroy(&task_allocators[i]) } + + task_timeouts: [dynamic]Task_Timeout = --- + task_timeouts, alloc_error = make([dynamic]Task_Timeout, 0, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task timeouts: %v", alloc_error) + defer delete(task_timeouts) + + failed_test_reason_map: map[int]string = --- + failed_test_reason_map, alloc_error = make(map[int]string, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for failed test reasons: %v", alloc_error) + defer delete(failed_test_reason_map) + + log_messages: [dynamic]Log_Message = --- + log_messages, alloc_error = make([dynamic]Log_Message, 0, RESERVED_LOG_MESSAGES) + fmt.assertf(alloc_error == nil, "Error allocating memory for log message queue: %v", alloc_error) + defer delete(log_messages) + + sorted_failed_test_reasons: [dynamic]int = --- + sorted_failed_test_reasons, alloc_error = make([dynamic]int, 0, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for sorted failed test reasons: %v", alloc_error) + defer delete(sorted_failed_test_reasons) + + when USE_CLIPBOARD { + clipboard_buffer: bytes.Buffer + bytes.buffer_init_allocator(&clipboard_buffer, 0, CLIPBOARD_BUFFER_SIZE) + defer bytes.buffer_destroy(&clipboard_buffer) + } + + // -- Setup initial tasks. + + // NOTE(Feoramund): This is the allocator that will be used by threads to + // persist log messages past their lifetimes. It has its own variable name + // in the event it needs to be changed from `safe_heap_allocator` without + // digging through the source to divine everywhere it is used for that. + shared_log_allocator := safe_heap_allocator + + context.allocator = safe_heap_allocator + + context.logger = { + procedure = runner_logger_proc, + data = &log_messages, + lowest_level = .Debug if ODIN_DEBUG else .Info, + options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + } + + run_index: int + + setup_tasks: for &data, task_index in task_data_slots { + setup_next_test: for run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + #no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel) + data.t._log_allocator = shared_log_allocator + data.allocator_index = task_index + + #no_bounds_check when TRACKING_MEMORY { + task_allocator := mem.tracking_allocator(&task_memory_trackers[task_index]) + } else { + task_allocator := mem.rollback_stack_allocator(&task_allocators[task_index]) + } + + thread.pool_add_task(&pool, task_allocator, run_test_task, &data, run_index) + + continue setup_tasks + } + } + + // -- Run tests. + + setup_signal_handler() + + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + + when FANCY_OUTPUT { + redraw_report(stdout, report) + draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) + } + + when TRACKING_MEMORY { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + pkg_log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } + + start_time := time.now() + + thread.pool_start(&pool) + main_loop: for !thread.pool_is_empty(&pool) { + + cycle_pool: for task in thread.pool_pop_done(&pool) { + data := cast(^Task_Data)(task.data) + + when TRACKING_MEMORY { + #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] + + write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + + pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + + mem.tracking_allocator_reset(tracker) + } + + free_all(task.allocator) + + if run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + data.t.error_count = 0 + + thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index) + } + } + + handle_events: for &task_channel in task_channels { + for ev in chan.try_recv(task_channel.channel) { + switch event in ev { + case Event_New_Test: + task_channel.test_index = event.test_index + + case Event_State_Change: + #no_bounds_check report.all_test_states[task_channel.test_index] = event.new_state + + #no_bounds_check it := internal_tests[task_channel.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + + #partial switch event.new_state { + case .Failed: + if task_channel.test_index not_in failed_test_reason_map { + failed_test_reason_map[task_channel.test_index] = ERROR_STRING_UNKNOWN + } + total_failure_count += 1 + total_done_count += 1 + case .Successful: + total_success_count += 1 + total_done_count += 1 + } + + when ODIN_DEBUG { + pkg_log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) + } + + pkg.last_change_state = event.new_state + pkg.last_change_name = it.name + pkg.frame_ready = false + + case Event_Set_Fail_Timeout: + _, alloc_error = append(&task_timeouts, Task_Timeout { + test_index = task_channel.test_index, + at_time = event.at_time, + location = event.location, + }) + fmt.assertf(alloc_error == nil, "Error appending to task timeouts: %v", alloc_error) + + case Event_Log_Message: + _, alloc_error = append(&log_messages, Log_Message { + level = event.level, + text = event.formatted_text, + time = event.time, + allocator = shared_log_allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + if event.level >= .Error { + // Save the message for the final summary. + if old_error, ok := failed_test_reason_map[task_channel.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[task_channel.test_index] = event.text + } else { + delete(event.text, shared_log_allocator) + } + } + } + } + + check_timeouts: for i := len(task_timeouts) - 1; i >= 0; i -= 1 { + #no_bounds_check timeout := &task_timeouts[i] + + if time.since(timeout.at_time) < 0 { + continue check_timeouts + } + + defer unordered_remove(&task_timeouts, i) + + #no_bounds_check if report.all_test_states[timeout.test_index] > .Running { + continue check_timeouts + } + + if !thread.pool_stop_task(&pool, timeout.test_index) { + // The task may have stopped a split second after we started + // checking, but we haven't handled the new state yet. + continue check_timeouts + } + + #no_bounds_check report.all_test_states[timeout.test_index] = .Failed + #no_bounds_check it := internal_tests[timeout.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + if old_error, ok := failed_test_reason_map[timeout.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[timeout.test_index] = ERROR_STRING_TIMEOUT + total_failure_count += 1 + total_done_count += 1 + + now := time.now() + _, alloc_error = append(&log_messages, Log_Message { + level = .Error, + text = format_log_text(.Error, ERROR_STRING_TIMEOUT, Default_Test_Logger_Opts, timeout.location, now), + time = now, + allocator = context.allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + find_task_data: for &data in task_data_slots { + if data.it.pkg == it.pkg && data.it.name == it.name { + end_t(&data.t) + break find_task_data + } + } + } + + if should_abort() { + fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.") + thread.pool_shutdown(&pool) + break main_loop + } + + // -- Redraw. + + when FANCY_OUTPUT { + if len(log_messages) == 0 && !needs_to_redraw(report) { + continue main_loop + } + + fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + } else { + if total_done_count != last_done_count { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + last_done_count = total_done_count + } + + if len(log_messages) == 0 { + continue main_loop + } + } + + // Because each thread has its own messenger channel, log messages + // arrive in chunks that are in-order, but when they're merged with the + // logs from other threads, they become out-of-order. + slice.stable_sort_by(log_messages[:], proc(a, b: Log_Message) -> bool { + return time.diff(a.time, b.time) > 0 + }) + + for message in log_messages { + fmt.wprintln(batch_writer, message.text) + delete(message.text, message.allocator) + } + + fmt.wprint(stderr, bytes.buffer_to_string(&batch_buffer)) + clear(&log_messages) + bytes.buffer_reset(&batch_buffer) + + when FANCY_OUTPUT { + redraw_report(batch_writer, report) + draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) + fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + } + } + + // -- All tests are complete, or the runner has been interrupted. + + thread.pool_join(&pool) + + finished_in := time.since(start_time) + + fmt.wprintf(batch_writer, + "\nFinished %i test%s in %v.", + total_done_count, + "" if total_done_count == 1 else "s", + finished_in) + + if total_done_count != total_test_count { + not_run_count := total_test_count - total_done_count + fmt.wprintf(batch_writer, + " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + not_run_count, + "test was" if not_run_count == 1 else "tests were") + } + + if total_success_count == total_test_count { + fmt.wprintfln(batch_writer, + " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + "The test was" if total_test_count == 1 else "All tests were") + } else if total_failure_count > 0 { + if total_failure_count == total_test_count { + fmt.wprintfln(batch_writer, + " %s " + SGR_FAILED + "failed." + SGR_RESET, + "The test" if total_test_count == 1 else "All tests") + } else { + fmt.wprintfln(batch_writer, + " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + total_failure_count, + "" if total_failure_count == 1 else "s") + } + + for test_index in failed_test_reason_map { + _, alloc_error = append(&sorted_failed_test_reasons, test_index) + fmt.assertf(alloc_error == nil, "Error appending to sorted failed test reasons: %v", alloc_error) + } + + slice.sort(sorted_failed_test_reasons[:]) + + for test_index in sorted_failed_test_reasons { + #no_bounds_check last_error := failed_test_reason_map[test_index] + #no_bounds_check it := internal_tests[test_index] + pkg_and_name := fmt.tprintf("%s.%s", it.pkg, it.name) + fmt.wprintfln(batch_writer, " - %- *[1]s\t%s", + pkg_and_name, + report.pkg_column_len + report.test_column_len, + last_error) + safe_delete_string(last_error, shared_log_allocator) + } + + if total_success_count > 0 { + when USE_CLIPBOARD { + clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer)) + fmt.wprint(clipboard_writer, "-define:test_select=") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) + } + + encoded_names := base64_encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) + + fmt.wprintf(batch_writer, + ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + + "\nThe name%s of the failed test%s been copied to your clipboard.", + encoded_names, + "" if total_failure_count == 1 else "s", + " has" if total_failure_count == 1 else "s have") + } else { + fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:test_select=", + "" if total_failure_count == 1 else "s") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name) + } + } + + fmt.wprintln(batch_writer) + } + } + + fmt.wprint(batch_writer, ansi.CSI + ansi.DECTCEM_SHOW) + + fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) + return total_success_count == total_test_count } diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin new file mode 100644 index 000000000..2dd0ff192 --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,19 @@ +//+private +//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +package testing + +import "base:intrinsics" +import "core:c/libc" + +@(private="file") +abort_flag: libc.sig_atomic_t + +setup_signal_handler :: proc() { + libc.signal(libc.SIGINT, proc "c" (sig: libc.int) { + intrinsics.atomic_add(&abort_flag, 1) + }) +} + +should_abort :: proc() -> bool { + return intrinsics.atomic_load(&abort_flag) > 0 +} diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin new file mode 100644 index 000000000..b2e2ea906 --- /dev/null +++ b/core/testing/signal_handler_other.odin @@ -0,0 +1,11 @@ +//+private +//+build js, wasi, freestanding +package testing + +setup_signal_handler :: proc() { + // Do nothing. +} + +should_abort :: proc() -> bool { + return false +} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index a8c5ffa48..30109304d 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -1,10 +1,11 @@ package testing -import "core:fmt" -import "core:io" -import "core:time" import "base:intrinsics" +import "base:runtime" +import pkg_log "core:log" import "core:reflect" +import "core:sync/chan" +import "core:time" _ :: reflect // alias reflect to nothing to force visibility for -vet @@ -22,44 +23,45 @@ Internal_Test :: struct { Internal_Cleanup :: struct { procedure: proc(rawptr), user_data: rawptr, + ctx: runtime.Context, } T :: struct { error_count: int, - w: io.Writer, + channel: Update_Channel_Sender, cleanups: [dynamic]Internal_Cleanup, + // This allocator is shared between the test runner and its threads for + // cloning log strings, so they can outlive the lifetime of individual + // tests during channel transmission. + _log_allocator: runtime.Allocator, + _fail_now: proc() -> !, } +@(deprecated="prefer `log.error`") error :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintln(t.w, ..args) - t.error_count += 1 + pkg_log.error(..args, location = loc) } +@(deprecated="prefer `log.errorf`") errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) - t.error_count += 1 + pkg_log.errorf(format, ..args, location = loc) } fail :: proc(t: ^T, loc := #caller_location) { - error(t, "FAIL", loc=loc) - t.error_count += 1 + pkg_log.error("FAIL", location=loc) } fail_now :: proc(t: ^T, msg := "", loc := #caller_location) { if msg != "" { - error(t, "FAIL:", msg, loc=loc) + pkg_log.error("FAIL:", msg, location=loc) } else { - error(t, "FAIL", loc=loc) + pkg_log.error("FAIL", location=loc) } - t.error_count += 1 if t._fail_now != nil { t._fail_now() } @@ -69,32 +71,34 @@ failed :: proc(t: ^T) -> bool { return t.error_count != 0 } +@(deprecated="prefer `log.info`") log :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintln(t.w, ..args) + pkg_log.info(..args, location = loc) } +@(deprecated="prefer `log.infof`") logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) + pkg_log.infof(format, ..args, location = loc) } -// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete -// cleanup procedures will be called in LIFO (last added, first called) order. +// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete. +// Cleanup procedures will be called in LIFO (last added, first called) order. +// Each procedure will use a copy of the context at the time of registering. cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { - append(&t.cleanups, Internal_Cleanup{procedure, user_data}) + append(&t.cleanups, Internal_Cleanup{procedure, user_data, context}) } expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { if !ok { - error(t, msg, loc=loc) + pkg_log.error(msg, location=loc) } return ok } expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { if !ok { - errorf(t, format, ..args, loc=loc) + pkg_log.errorf(format, ..args, location=loc) } return ok } @@ -102,12 +106,15 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { - errorf(t, "expected %v, got %v", expected, value, loc=loc) + pkg_log.errorf("expected %v, got %v", expected, value, location=loc) } return ok } set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - _fail_timeout(t, duration, loc) + chan.send(t.channel, Event_Set_Fail_Timeout { + at_time = time.time_add(time.now(), duration), + location = loc, + }) } From d03024088aec0238ed236dd02987f8f8e8734958 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:04:49 -0400 Subject: [PATCH 021/151] Remove unneeded code --- core/testing/runner_other.odin | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin index f3271d209..29f338828 100644 --- a/core/testing/runner_other.odin +++ b/core/testing/runner_other.odin @@ -2,13 +2,7 @@ //+build !windows package testing -import "core:time" - run_internal_test :: proc(t: ^T, it: Internal_Test) { // TODO(bill): Catch panics on other platforms it.p(t) } - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - -} \ No newline at end of file From 87ea4a2652e99f44a286fe6328659fe7e6e5f7b9 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:05:43 -0400 Subject: [PATCH 022/151] Temporarily disable Windows-specific test runner I do not have a Windows machine to test the refactored test runner, and I am unsure if it would even run correctly on Windows without this disabled. --- core/testing/runner_windows.odin | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 15264355b..23dab4f31 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -2,6 +2,12 @@ //+build windows package testing +run_internal_test :: proc(t: ^T, it: Internal_Test) { + it.p(t) +} + +// Temporarily disabled during multi-threaded test runner refactor. +/* import win32 "core:sys/windows" import "base:runtime" import "base:intrinsics" @@ -233,3 +239,4 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { return } +*/ From 852f694bee4469c009cb02a850e02fe1c7165477 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:42:35 -0400 Subject: [PATCH 023/151] Get tests passing again `T` no longer has a writer assigned to it. `test_core_cbor.odin` has global state and is run with `odin test`, so I've set it to use only one thread. --- tests/core/build.bat | 2 +- tests/core/compress/test_core_compress.odin | 8 ++++---- tests/core/text/match/test_core_text_match.odin | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index 7871e52e2..4755e0a44 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -25,7 +25,7 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:test_threads=1 -define:test_fancy=false || exit /b %PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index ac7555e9a..db6f3d06b 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -21,7 +21,7 @@ import "core:fmt" import "core:mem" import "core:os" -import "core:io" +// import "core:io" TEST_count := 0 TEST_fail := 0 @@ -45,8 +45,8 @@ when ODIN_TEST { } main :: proc() { - w := io.to_writer(os.stream_from_handle(os.stdout)) - t := testing.T{w=w} + // w := io.to_writer(os.stream_from_handle(os.stdout)) + t := testing.T{}//{w=w} zlib_test(&t) gzip_test(&t) shoco_test(&t) @@ -195,4 +195,4 @@ shoco_test :: proc(t: ^testing.T) { size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") } -} \ No newline at end of file +} diff --git a/tests/core/text/match/test_core_text_match.odin b/tests/core/text/match/test_core_text_match.odin index b72190f78..eadd17433 100644 --- a/tests/core/text/match/test_core_text_match.odin +++ b/tests/core/text/match/test_core_text_match.odin @@ -14,7 +14,7 @@ failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { TEST_count += 1 if !ok { - fmt.wprintf(t.w, "%v: ", loc) + fmt.printf(/*t.w,*/ "%v: ", loc) t.error_count += 1 TEST_fail += 1 } @@ -25,7 +25,7 @@ failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { expect :: testing.expect logf :: proc(t: ^testing.T, format: string, args: ..any) { - fmt.wprintf(t.w, format, ..args) + fmt.printf(/*t.w,*/ format, ..args) } // find correct byte offsets @@ -380,7 +380,7 @@ main :: proc() { t: testing.T stream := os.stream_from_handle(os.stdout) w := io.to_writer(stream) - t.w = w + // t.w = w test_find(&t) test_match(&t) @@ -396,4 +396,4 @@ main :: proc() { if TEST_fail > 0 { os.exit(1) } -} \ No newline at end of file +} From eb3d6d7d75cf8b2de1bfddf03820ee60ce595b05 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:16:24 +0200 Subject: [PATCH 024/151] Update `core:image` tests to use new runner. --- tests/core/Makefile | 4 +- tests/core/build.bat | 4 +- tests/core/image/build.bat | 6 +- tests/core/image/test_core_image.odin | 301 ++++++++++---------------- 4 files changed, 120 insertions(+), 195 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 9026ed3d9..a740b3d7c 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true COLLECTION=-collection:tests=.. all: all_bsd \ @@ -34,7 +34,7 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image $(COMMON) -out:test_core_image + $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image compress_test: $(ODIN) run compress $(COMMON) -out:test_core_compress diff --git a/tests/core/build.bat b/tests/core/build.bat index 4755e0a44..4d981be11 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -define:test_progress_width=12 -out:test_core_image.exe || exit /b echo --- echo Running core:math tests diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat index 03ee6b9a5..35d1c64e9 100644 --- a/tests/core/image/build.bat +++ b/tests/core/image/build.bat @@ -1,4 +1,2 @@ -@echo off -pushd .. -odin run image -popd \ No newline at end of file +@echo off +odin test . -define:test_track_memory=true -define:test_progress_width=12 -vet -strict-style \ No newline at end of file diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index ae92ca617..e3bef6c9d 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -20,49 +20,15 @@ import "core:image/tga" import "core:bytes" import "core:hash" -import "core:fmt" import "core:strings" - import "core:mem" -import "core:os" import "core:time" - import "base:runtime" -TEST_SUITE_PATH :: "assets/PNG" +TEST_SUITE_PATH :: "assets/PNG" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} I_Error :: image.Error -main :: proc() { - t := testing.T{} - png_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - PNG_Test :: struct { file: string, tests: []struct { @@ -72,7 +38,6 @@ PNG_Test :: struct { hash: u32, }, } - Default :: image.Options{} Alpha_Add :: image.Options{.alpha_add_if_missing} Premul_Drop :: image.Options{.alpha_premultiply, .alpha_drop_if_present} @@ -82,7 +47,7 @@ Blend_BG_Keep :: image.Options{.blend_background, .alpha_add_if_missing} Return_Metadata :: image.Options{.return_metadata} No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata} -PNG_Dims :: struct { +PNG_Dims :: struct { width: int, height: int, channels: int, @@ -1430,38 +1395,66 @@ Expected_Text := map[string]map[string]png.Text { } @test -png_test :: proc(t: ^testing.T) { - - total_tests := 0 - total_expected := 235 - - PNG_Suites := [][]PNG_Test{ - Basic_PNG_Tests, - Interlaced_PNG_Tests, - Odd_Sized_PNG_Tests, - PNG_bKGD_Tests, - PNG_tRNS_Tests, - PNG_Filter_Tests, - PNG_Varied_IDAT_Tests, - PNG_ZLIB_Levels_Tests, - PNG_sPAL_Tests, - PNG_Ancillary_Tests, - Corrupt_PNG_Tests, - - No_Postprocesing_Tests, - - } - - for suite in PNG_Suites { - total_tests += run_png_suite(t, suite) - } - - error := fmt.tprintf("Expected %v PNG tests, %v ran.", total_expected, total_tests) - expect(t, total_tests == total_expected, error) +png_test_basic :: proc(t: ^testing.T) { + run_png_suite(t, Basic_PNG_Tests) } -run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { +@test +png_test_interlaced :: proc(t: ^testing.T) { + run_png_suite(t, Interlaced_PNG_Tests) +} +@test +png_test_odd_sized :: proc(t: ^testing.T) { + run_png_suite(t, Odd_Sized_PNG_Tests) +} + +@test +png_test_bKGD :: proc(t: ^testing.T) { + run_png_suite(t, PNG_bKGD_Tests) +} + +@test +png_test_tRNS :: proc(t: ^testing.T) { + run_png_suite(t, PNG_tRNS_Tests) +} + +@test +png_test_sPAL :: proc(t: ^testing.T) { + run_png_suite(t, PNG_sPAL_Tests) +} + +@test +png_test_filter :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Filter_Tests) +} + +@test +png_test_varied_idat :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Varied_IDAT_Tests) +} + +@test +png_test_zlib_levels :: proc(t: ^testing.T) { + run_png_suite(t, PNG_ZLIB_Levels_Tests) +} + +@test +png_test_ancillary :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Ancillary_Tests) +} + +@test +png_test_corrupt :: proc(t: ^testing.T) { + run_png_suite(t, Corrupt_PNG_Tests) +} + +@test +png_test_no_postproc :: proc(t: ^testing.T) { + run_png_suite(t, No_Postprocesing_Tests) +} + +run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { context = runtime.default_context() for file in suite { @@ -1472,9 +1465,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { count := 0 for test in file.tests { - count += 1 - subtotal += 1 - passed := false + count += 1 track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) @@ -1482,29 +1473,21 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { img, err = png.load(test_file, test.options) - error := fmt.tprintf("%v failed with %v.", test_file, err) - - passed = (test.expected_error == nil && err == nil) || (test.expected_error == err) - - expect(t, passed, error) + passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) + testing.expectf(t, passed, "%v failed with %v.", test_file, err) if err == nil { // No point in running the other tests if it didn't load. pixels := bytes.buffer_to_bytes(&img.pixels) // This struct compare fails at -opt:2 if PNG_Dims is not #packed. - - dims := PNG_Dims{img.width, img.height, img.channels, img.depth} - error = fmt.tprintf("%v has %v, expected: %v.", file.file, dims, test.dims) - + dims := PNG_Dims{img.width, img.height, img.channels, img.depth} dims_pass := test.dims == dims - expect(t, dims_pass, error) - + testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims) passed &= dims_pass - png_hash := hash.crc32(pixels) - error = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) - expect(t, test.hash == png_hash, error) + png_hash := hash.crc32(pixels) + testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) passed &= test.hash == png_hash @@ -1515,19 +1498,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&qoi_buffer) qoi_save_err := qoi.save(&qoi_buffer, img) - error = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) - expect(t, qoi_save_err == nil, error) + testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) if qoi_save_err == nil { qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:]) defer qoi.destroy(qoi_img) - error = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) - expect(t, qoi_load_err == nil, error) + testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) qoi_hash := hash.crc32(qoi_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) - expect(t, qoi_hash == png_hash, error) + testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) } } @@ -1537,19 +1517,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&tga_buffer) tga_save_err := tga.save(&tga_buffer, img) - error = fmt.tprintf("%v test %v TGA save failed with %v.", file.file, count, tga_save_err) - expect(t, tga_save_err == nil, error) - + testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err) if tga_save_err == nil { tga_img, tga_load_err := tga.load(tga_buffer.buf[:]) defer tga.destroy(tga_img) - error = fmt.tprintf("%v test %v TGA load failed with %v.", file.file, count, tga_load_err) - expect(t, tga_load_err == nil, error) + testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err) tga_hash := hash.crc32(tga_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) - expect(t, tga_hash == png_hash, error) + testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) } } @@ -1558,22 +1534,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1587,22 +1559,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1655,21 +1623,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { float_pbm_buf, float_pbm_save_err := pbm.save_to_buffer(float_img, pbm_info) defer delete(float_pbm_buf) - error = fmt.tprintf("%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) - expect(t, float_pbm_save_err == nil, error) + testing.expectf(t, float_pbm_save_err == nil, "%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) if float_pbm_save_err == nil { // Load float image and compare. float_pbm_img, float_pbm_load_err := pbm.load(float_pbm_buf) defer pbm.destroy(float_pbm_img) - error = fmt.tprintf("%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) - expect(t, float_pbm_load_err == nil, error) + testing.expectf(t, float_pbm_load_err == nil, "%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) load_float := mem.slice_data_cast([]f32, float_pbm_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) - expect(t, len(load_float) == len(orig_float), error) + testing.expectf(t, len(load_float) == len(orig_float), "%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) // Compare floats equal := true @@ -1679,15 +1644,13 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { break } } - error = fmt.tprintf("%v test %v PFM loaded floats to match", file.file, count) - expect(t, equal, error) + testing.expectf(t, equal, "%v test %v PFM loaded floats to match", file.file, count) } } } } if .return_metadata in test.options { - if v, ok := img.metadata.(^image.PNG_Info); ok { for c in v.chunks { #partial switch(c.header.type) { @@ -1696,8 +1659,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "pp0n2c16", "pp0n6a08": gamma, gamma_ok := png.gamma(c) expected_gamma := f32(1.0) - error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) - expect(t, gamma == expected_gamma && gamma_ok, error) + testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) } case .PLTE: switch(file.file) { @@ -1705,8 +1667,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { plte, plte_ok := png.plte(c) expected_plte_len := u16(216) - error = fmt.tprintf("%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) - expect(t, expected_plte_len == plte.used && plte_ok, error) + testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) } case .sPLT: switch(file.file) { @@ -1714,12 +1675,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { splt, splt_ok := png.splt(c) expected_splt_len := u16(216) - error = fmt.tprintf("%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) - expect(t, expected_splt_len == splt.used && splt_ok, error) + testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) expected_splt_name := "six-cube" - error = fmt.tprintf("%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) - expect(t, expected_splt_name == splt.name && splt_ok, error) + testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) png.splt_destroy(splt) } @@ -1733,48 +1692,37 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { g = png.CIE_1931{x = 0.3000, y = 0.6000}, b = png.CIE_1931{x = 0.1500, y = 0.0600}, } - error = fmt.tprintf("%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) - expect(t, expected_chrm == chrm && chrm_ok, error) + testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) } case .pHYs: phys, phys_ok := png.phys(c) - phys_err := "%v test %v cHRM is %v, expected %v." switch (file.file) { case "cdfn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdhn2c08": expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdsn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdun2c08": expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) } case .hIST: hist, hist_ok := png.hist(c) - hist_err := "%v test %v hIST has %v entries, expected %v." switch (file.file) { case "ch1n3p04": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 15) - expect(t, hist.used == 15 && hist_ok, error) + testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15) case "ch2n3p08": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 256) - expect(t, hist.used == 256 && hist_ok, error) + testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256) } case .tIME: png_time, png_time_ok := png.time(c) - time_err := "%v test %v tIME was %v, expected %v." expected_time: png.tIME core_time, core_time_ok := png.core_time(c) - time_core_err := "%v test %v tIME->core:time is %v, expected %v." expected_core: time.Time switch(file.file) { @@ -1789,14 +1737,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { expected_core = time.Time{_nsec = 946684799000000000} } - error = fmt.tprintf(time_err, file.file, count, png_time, expected_time) - expect(t, png_time == expected_time && png_time_ok, error) - - error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core) - expect(t, core_time == expected_core && core_time_ok, error) + testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time) + testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core) case .sBIT: sbit, sbit_ok := png.sbit(c) - sbit_err := "%v test %v sBIT was %v, expected %v." expected_sbit: [4]u8 switch (file.file) { @@ -1815,8 +1759,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04": expected_sbit = [4]u8{ 4, 4, 4, 0} } - error = fmt.tprintf(sbit_err, file.file, count, sbit, expected_sbit) - expect(t, sbit == expected_sbit && sbit_ok, error) + testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit) case .tEXt, .zTXt: text, text_ok := png.text(c) defer png.text_destroy(text) @@ -1828,8 +1771,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test_text := Expected_Text[file.file][text.keyword].text - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) - expect(t, text.text == test_text && text_ok, error) + testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) } } } @@ -1842,74 +1784,59 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctfn0g04": // international UTF-8, finnish if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctgn0g04": // international UTF-8, greek if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "cthn0g04": // international UTF-8, hindi if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctjn0g04": // international UTF-8, japanese if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } } case .eXIf: if file.file == "exif2c08" { // chunk with jpeg exif data exif, exif_ok := png.exif(c) - error = fmt.tprintf("%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) - error_len := fmt.tprintf("%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) - expect(t, exif.byte_order == .big_endian && exif_ok, error) - expect(t, len(exif.data) == 978 && exif_ok, error_len) + testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) + testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) } } } } } } - png.destroy(img) - - for _, v in track.allocation_map { - error = fmt.tprintf("%v test %v leaked %v bytes @ loc %v.", file.file, count, v.size, v.location) - expect(t, false, error) - } } } - - return -} +} \ No newline at end of file From 22c092f846b0a7654b71d55c4e37980cd155f781 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:22:02 +0200 Subject: [PATCH 025/151] Delete duplicated flag. --- tests/core/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index a740b3d7c..4fb046449 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -46,10 +46,10 @@ strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings hash_test: - $(ODIN) run hash $(COMMON) -o:speed -no-bounds-check -out:test_hash + $(ODIN) run hash $(COMMON) -o:speed -out:test_hash crypto_test: - $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -no-bounds-check -out:test_crypto + $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -out:test_crypto noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise From e3181c13c66b68ae2c0788d43417316a45b58b1d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:53:27 +0200 Subject: [PATCH 026/151] Update `core:compress` tests --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/compress/test_core_compress.odin | 101 ++++---------------- 3 files changed, 18 insertions(+), 87 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4fb046449..ef76bbbb8 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -37,7 +37,7 @@ image_test: $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image compress_test: - $(ODIN) run compress $(COMMON) -out:test_core_compress + $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress container_test: $(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container diff --git a/tests/core/build.bat b/tests/core/build.bat index 4d981be11..e33b49531 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -6,7 +6,7 @@ python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -define:test_progress_width=3 -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index db6f3d06b..4ab63ae67 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -15,47 +15,7 @@ import "core:testing" import "core:compress/zlib" import "core:compress/gzip" import "core:compress/shoco" - import "core:bytes" -import "core:fmt" - -import "core:mem" -import "core:os" -// import "core:io" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - // w := io.to_writer(os.stream_from_handle(os.stdout)) - t := testing.T{}//{w=w} - zlib_test(&t) - gzip_test(&t) - shoco_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test zlib_test :: proc(t: ^testing.T) { @@ -80,26 +40,14 @@ zlib_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := zlib.inflate(ODIN_DEMO, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := zlib.inflate(ODIN_DEMO, &buf) - - expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") + testing.expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") s := bytes.buffer_to_string(&buf) - expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") - - expect(t, len(s) == 438, "ZLIB result has an unexpected length.") - + testing.expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") + testing.expect(t, len(s) == 438, "ZLIB result has an unexpected length.") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("ZLIB test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -117,24 +65,12 @@ gzip_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := gzip.load(TEST, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := gzip.load(TEST, &buf) // , 438); - - expect(t, err == nil, "GZIP failed to decompress TEST") - s := bytes.buffer_to_string(&buf) - - expect(t, s == "payload", "GZIP result wasn't 'payload'") + testing.expect(t, err == nil, "GZIP failed to decompress TEST") + testing.expect(t, bytes.buffer_to_string(&buf) == "payload", "GZIP result wasn't 'payload'") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("GZIP test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -168,31 +104,26 @@ shoco_test :: proc(t: ^testing.T) { defer delete(buffer) size, err := shoco.decompress(v.compressed, buffer[:]) - msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "Expected `decompress` to return `nil`, got %v", err) - msg = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw) - expect(t, size == expected_raw, msg) - expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.") + testing.expectf(t, size == expected_raw, "Decompressed %v bytes into %v. Expected to decompress into %v bytes", len(v.compressed), size, expected_raw) + testing.expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match") size, err = shoco.compress(string(v.raw), buffer[:]) - expect(t, err == nil, "Expected `compress` to return `nil`.") + testing.expect(t, err == nil, "Expected `compress` to return `nil`.") - msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed) - expect(t, size == expected_compressed, msg) + testing.expectf(t, size == expected_compressed, "Compressed %v bytes into %v. Expected to compress into %v bytes", expected_raw, size, expected_compressed) size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10]) - msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10]) - msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.") + testing.expectf(t, err == .Stream_Too_Short, "Insufficient data after pack returned %v, expected `.Stream_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") + testing.expectf(t, err == .Stream_Too_Short, "No more data after non-ASCII sentinel returned %v, expected `.Stream_Too_Short`", err) } } From a463e282db7931226c85b7562a4b4a716399b21b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 19:35:03 +0200 Subject: [PATCH 027/151] Update `core:container` tests --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/container/test_core_avl.odin | 71 +++++++-------- tests/core/container/test_core_container.odin | 26 ------ tests/core/container/test_core_rbtree.odin | 90 ++++++++++--------- .../core/container/test_core_small_array.odin | 67 +++++++------- 6 files changed, 119 insertions(+), 139 deletions(-) delete mode 100644 tests/core/container/test_core_container.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index ef76bbbb8..fff46fb69 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -40,7 +40,7 @@ compress_test: $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress container_test: - $(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container + $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index e33b49531..07432a657 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -11,7 +11,7 @@ echo --- echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% define:test_progress_width=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests diff --git a/tests/core/container/test_core_avl.odin b/tests/core/container/test_core_avl.odin index 2244ab7f6..37b21a6be 100644 --- a/tests/core/container/test_core_avl.odin +++ b/tests/core/container/test_core_avl.odin @@ -4,22 +4,21 @@ import "core:container/avl" import "core:math/rand" import "core:slice" import "core:testing" -import "core:fmt" -import tc "tests:common" +import "core:log" @(test) test_avl :: proc(t: ^testing.T) { - tc.log(t, fmt.tprintf("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed)) + log.infof("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed) // Initialization. tree: avl.Tree(int) avl.init(&tree, slice.cmp_proc(int)) - tc.expect(t, avl.len(&tree) == 0, "empty: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "empty: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "empty: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "empty: last should be nil") iter := avl.iterator(&tree, avl.Direction.Forward) - tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") r: rand.Rand rand.init(&r, random_seed) @@ -27,30 +26,32 @@ test_avl :: proc(t: ^testing.T) { // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. inserted_map := make(map[int]^avl.Node(int)) + defer delete(inserted_map) for i := 0; i < NR_INSERTS; i += 1 { v := int(rand.uint32(&r) & 0x1f) existing_node, in_map := inserted_map[v] n, ok, _ := avl.find_or_insert(&tree, v) - tc.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") + testing.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") if ok { inserted_map[v] = n } else { - tc.expect(t, existing_node == n, "insert: expecting existing node") + testing.expect(t, existing_node == n, "insert: expecting existing node") } } nrEntries := len(inserted_map) - tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after") + testing.expect(t, avl.len(&tree) == nrEntries, "insert: len after") validate_avl(t, &tree) // Ensure that all entries can be found. for k, v in inserted_map { - tc.expect(t, v == avl.find(&tree, k), "Find(): Node") - tc.expect(t, k == v.value, "Find(): Node value") + testing.expect(t, v == avl.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.value, "Find(): Node value") } // Test the forward/backward iterators. inserted_values: [dynamic]int + defer delete(inserted_values) for k in inserted_map { append(&inserted_values, k) } @@ -60,38 +61,38 @@ test_avl :: proc(t: ^testing.T) { visited: int for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/forward: value") - tc.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") + testing.expect(t, inserted_values[idx] == v, "iterator/forward: value") + testing.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/forward: visited") + testing.expect(t, visited == nrEntries, "iterator/forward: visited") slice.reverse(inserted_values[:]) iter = avl.iterator(&tree, avl.Direction.Backward) visited = 0 for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/backward: value") + testing.expect(t, inserted_values[idx] == v, "iterator/backward: value") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/backward: visited") + testing.expect(t, visited == nrEntries, "iterator/backward: visited") // Test removal. rand.shuffle(inserted_values[:], &r) for v, i in inserted_values { node := avl.find(&tree, v) - tc.expect(t, node != nil, "remove: find (pre)") + testing.expect(t, node != nil, "remove: find (pre)") ok := avl.remove(&tree, v) - tc.expect(t, ok, "remove: succeeds") - tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") validate_avl(t, &tree) - tc.expect(t, nil == avl.find(&tree, v), "remove: find (post") + testing.expect(t, nil == avl.find(&tree, v), "remove: find (post") } - tc.expect(t, avl.len(&tree) == 0, "remove: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "remove: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "remove: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "remove: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "remove: last should be nil") // Refill the tree. for v in inserted_values { @@ -104,25 +105,25 @@ test_avl :: proc(t: ^testing.T) { v := node.value ok := avl.iterator_remove(&iter) - tc.expect(t, ok, "iterator/remove: success") + testing.expect(t, ok, "iterator/remove: success") ok = avl.iterator_remove(&iter) - tc.expect(t, !ok, "iterator/remove: redundant removes should fail") + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - tc.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") - tc.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + testing.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") + testing.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = avl.iterator_next(&iter) - tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") - tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") + testing.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") validate_avl(t, &tree) } - tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") + testing.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") avl.destroy(&tree) - tc.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") + testing.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") } @(private) @@ -141,10 +142,10 @@ tree_check_invariants :: proc( } // Validate the parent pointer. - tc.expect(t, parent == node._parent, "invalid parent pointer") + testing.expect(t, parent == node._parent, "invalid parent pointer") // Validate that the balance factor is -1, 0, 1. - tc.expect( + testing.expect( t, node._balance == -1 || node._balance == 0 || node._balance == 1, "invalid balance factor", @@ -155,7 +156,7 @@ tree_check_invariants :: proc( r_height := tree_check_invariants(t, tree, node._right, node) // Validate the AVL invariant and the balance factor. - tc.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") + testing.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") if l_height > r_height { return l_height + 1 } diff --git a/tests/core/container/test_core_container.odin b/tests/core/container/test_core_container.odin deleted file mode 100644 index 7dd4a3628..000000000 --- a/tests/core/container/test_core_container.odin +++ /dev/null @@ -1,26 +0,0 @@ -package test_core_container - -import "core:fmt" -import "core:testing" - -import tc "tests:common" - -expect_equal :: proc(t: ^testing.T, the_slice, expected: []int, loc := #caller_location) { - _eq :: proc(a, b: []int) -> bool { - if len(a) != len(b) do return false - for a, i in a { - if b[i] != a do return false - } - return true - } - tc.expect(t, _eq(the_slice, expected), fmt.tprintf("Expected %v, got %v\n", the_slice, expected), loc) -} - -main :: proc() { - t := testing.T{} - - test_avl(&t) - test_rbtree(&t) - test_small_array(&t) - tc.report(&t) -} diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index 89742b1d0..a4151d120 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -3,14 +3,16 @@ package test_core_container import rb "core:container/rbtree" import "core:math/rand" import "core:testing" -import "core:fmt" import "base:intrinsics" import "core:mem" import "core:slice" -import tc "tests:common" +import "core:log" -RANDOM_SEED :: #config(RANDOM_SEED, 0) -random_seed := u64(intrinsics.read_cycle_counter()) when RANDOM_SEED == 0 else u64(RANDOM_SEED) +@(private) +_RANDOM_SEED :: #config(RANDOM_SEED, u64(0)) + +// Exported +random_seed := u64(intrinsics.read_cycle_counter()) when _RANDOM_SEED == 0 else u64(_RANDOM_SEED) test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { track: mem.Tracking_Allocator @@ -21,15 +23,15 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { r: rand.Rand rand.init(&r, random_seed) - tc.log(t, fmt.tprintf("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed)) + log.infof("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed) tree: rb.Tree(Key, Value) rb.init(&tree) - tc.expect(t, rb.len(&tree) == 0, "empty: len should be 0") - tc.expect(t, rb.first(&tree) == nil, "empty: first should be nil") - tc.expect(t, rb.last(&tree) == nil, "empty: last should be nil") + testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil") iter := rb.iterator(&tree, .Forward) - tc.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + testing.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. @@ -45,27 +47,27 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { existing_node, in_map := inserted_map[k] n, inserted, _ := rb.find_or_insert(&tree, k, v) - tc.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup") + testing.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup") if inserted { inserted_map[k] = n } else { - tc.expect(t, existing_node == n, "insert: expecting existing node") + testing.expect(t, existing_node == n, "insert: expecting existing node") } } entry_count := len(inserted_map) - tc.expect(t, rb.len(&tree) == entry_count, "insert: len after") + testing.expect(t, rb.len(&tree) == entry_count, "insert: len after") validate_rbtree(t, &tree) first := rb.first(&tree) last := rb.last(&tree) - tc.expect(t, first != nil && first.key == min_key, fmt.tprintf("insert: first should be present with key %v", min_key)) - tc.expect(t, last != nil && last.key == max_key, fmt.tprintf("insert: last should be present with key %v", max_key)) + testing.expectf(t, first != nil && first.key == min_key, "insert: first should be present with key %v", min_key) + testing.expectf(t, last != nil && last.key == max_key, "insert: last should be present with key %v", max_key) // Ensure that all entries can be found. for k, v in inserted_map { - tc.expect(t, v == rb.find(&tree, k), "Find(): Node") - tc.expect(t, k == v.key, "Find(): Node key") + testing.expect(t, v == rb.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.key, "Find(): Node key") } // Test the forward/backward iterators. @@ -79,21 +81,21 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { visited: int for node in rb.iterator_next(&iter) { k, idx := node.key, visited - tc.expect(t, inserted_keys[idx] == k, "iterator/forward: key") - tc.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get") + testing.expect(t, inserted_keys[idx] == k, "iterator/forward: key") + testing.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get") visited += 1 } - tc.expect(t, visited == entry_count, "iterator/forward: visited") + testing.expect(t, visited == entry_count, "iterator/forward: visited") slice.reverse(inserted_keys[:]) iter = rb.iterator(&tree, rb.Direction.Backward) visited = 0 for node in rb.iterator_next(&iter) { k, idx := node.key, visited - tc.expect(t, inserted_keys[idx] == k, "iterator/backward: key") + testing.expect(t, inserted_keys[idx] == k, "iterator/backward: key") visited += 1 } - tc.expect(t, visited == entry_count, "iterator/backward: visited") + testing.expect(t, visited == entry_count, "iterator/backward: visited") // Test removal (and on_remove callback) rand.shuffle(inserted_keys[:], &r) @@ -104,19 +106,19 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { } for k, i in inserted_keys { node := rb.find(&tree, k) - tc.expect(t, node != nil, "remove: find (pre)") + testing.expect(t, node != nil, "remove: find (pre)") ok := rb.remove(&tree, k) - tc.expect(t, ok, "remove: succeeds") - tc.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") validate_rbtree(t, &tree) - tc.expect(t, nil == rb.find(&tree, k), "remove: find (post") + testing.expect(t, nil == rb.find(&tree, k), "remove: find (post") } - tc.expect(t, rb.len(&tree) == 0, "remove: len should be 0") - tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)) - tc.expect(t, rb.first(&tree) == nil, "remove: first should be nil") - tc.expect(t, rb.last(&tree) == nil, "remove: last should be nil") + testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) + testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil") // Refill the tree. for k in inserted_keys { @@ -130,32 +132,32 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { k := node.key ok := rb.iterator_remove(&iter) - tc.expect(t, ok, "iterator/remove: success") + testing.expect(t, ok, "iterator/remove: success") ok = rb.iterator_remove(&iter) - tc.expect(t, !ok, "iterator/remove: redundant removes should fail") + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - tc.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") - tc.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") + testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = rb.iterator_next(&iter) - tc.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") - tc.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") + testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") validate_rbtree(t, &tree) } - tc.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") + testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") rb.destroy(&tree) - tc.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") - tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)) + testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) // print_tree_node(tree._root) delete(inserted_map) delete(inserted_keys) - tc.expect(t, len(track.allocation_map) == 0, fmt.tprintf("Expected 0 leaks, have %v", len(track.allocation_map))) - tc.expect(t, len(track.bad_free_array) == 0, fmt.tprintf("Expected 0 bad frees, have %v", len(track.bad_free_array))) + testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map)) + testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array)) return } @@ -194,7 +196,7 @@ validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) { } verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { - tc.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") + testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") if n == nil { return } @@ -203,14 +205,14 @@ verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { } verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) { - tc.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.") + testing.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.") } verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { if rb.node_color(n) == .Red { // A red node's left, right and parent should be black all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black - tc.expect(t, all_black, "Property #3: Red node's children + parent must be black.") + testing.expect(t, all_black, "Property #3: Red node's children + parent must be black.") } if n == nil { return @@ -233,7 +235,7 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu if path_black_count^ == -1 { path_black_count^ = black_count } else { - tc.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.") + testing.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.") } return } diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin index 78998de16..580df793e 100644 --- a/tests/core/container/test_core_small_array.odin +++ b/tests/core/container/test_core_small_array.odin @@ -3,44 +3,47 @@ package test_core_container import "core:testing" import "core:container/small_array" -import tc "tests:common" - -@(test) -test_small_array :: proc(t: ^testing.T) { - tc.log(t, "Testing small_array") - - test_small_array_removes(t) - test_small_array_inject_at(t) -} - @(test) test_small_array_removes :: proc(t: ^testing.T) { - array: small_array.Small_Array(10, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(10, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - small_array.ordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - small_array.ordered_remove(&array, 5) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 }) - small_array.ordered_remove(&array, 6) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 }) - small_array.unordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 }) - small_array.unordered_remove(&array, 2) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4, 5 }) - small_array.unordered_remove(&array, 4) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4 }) + small_array.ordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + small_array.ordered_remove(&array, 5) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })) + small_array.ordered_remove(&array, 6) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })) + small_array.unordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 })) + small_array.unordered_remove(&array, 2) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4, 5 })) + small_array.unordered_remove(&array, 4) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4 })) } @(test) test_small_array_inject_at :: proc(t: ^testing.T) { - array: small_array.Small_Array(13, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(13, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - tc.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 }) + testing.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) +} + +slice_equal :: proc(a, b: []int) -> bool { + if len(a) != len(b) { + return false + } + + for a, i in a { + if b[i] != a { + return false + } + } + return true } From a0b2ea6d6e327dfc345b0f07c6188ae64abc2d95 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 22:12:33 +0200 Subject: [PATCH 028/151] Update `tests\core\crypto` --- tests/core/Makefile | 6 +- tests/core/build.bat | 2 +- tests/core/crypto/test_core_crypto.odin | 96 ++---- tests/core/crypto/test_core_crypto_aes.odin | 125 ++++---- .../crypto/test_core_crypto_ecc25519.odin | 293 +++++++----------- tests/core/crypto/test_core_crypto_hash.odin | 98 +++--- tests/core/crypto/test_core_crypto_kdf.odin | 54 ++-- tests/core/crypto/test_core_crypto_mac.odin | 82 ++--- .../test_core_crypto_sha3_variants.odin | 96 +++--- tests/core/crypto/test_crypto_benchmark.odin | 68 ++-- tests/core/image/test_core_image.odin | 12 +- 11 files changed, 355 insertions(+), 577 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index fff46fb69..4dede0370 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -42,15 +42,15 @@ compress_test: container_test: $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container +crypto_test: + $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto + strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings hash_test: $(ODIN) run hash $(COMMON) -o:speed -out:test_hash -crypto_test: - $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -out:test_crypto - noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise diff --git a/tests/core/build.bat b/tests/core/build.bat index 07432a657..fc5e2dc3f 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -16,7 +16,7 @@ echo --- echo --- echo Running core:crypto tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% %COLLECTION% -out:test_crypto.exe || exit /b +%PATH_TO_ODIN% test crypto %COMMON% define:test_progress_width=18 -o:speed -out:test_crypto.exe || exit /b echo --- echo Running core:encoding tests diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 1f7bcece3..f3f76646b 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -13,41 +13,20 @@ package test_core_crypto */ import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" +import "base:runtime" +import "core:log" import "core:crypto" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_rand_bytes(&t) - - test_hash(&t) - test_mac(&t) - test_kdf(&t) // After hash/mac tests because those should pass first. - test_ecc25519(&t) - - test_aes(&t) - test_chacha20(&t) - test_chacha20poly1305(&t) - test_sha3_variants(&t) - - bench_crypto(&t) - - tc.report(&t) -} - _PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." @(test) test_chacha20 :: proc(t: ^testing.T) { - tc.log(t, "Testing (X)ChaCha20") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) @@ -90,14 +69,12 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) xkey := [chacha20.KEY_SIZE]byte { @@ -137,21 +114,17 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == xciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - xciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + xciphertext_str, + derived_ciphertext_str, ) } @(test) test_chacha20poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing chacha20poly1205") - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) aad := [12]byte { @@ -209,25 +182,21 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected tag %s for encrypt(aad, plaintext), but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected tag %s for encrypt(aad, plaintext), but got %s instead", + tag_str, + derived_tag_str, ) derived_plaintext: [114]byte @@ -240,15 +209,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ciphertext[:], ) derived_plaintext_str := string(derived_plaintext[:]) - tc.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") - tc.expect( + testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") + testing.expectf( t, derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - fmt.tprintf( - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, - ), + "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", + _PLAINTEXT_SUNSCREEN_STR, + derived_plaintext_str, ) derived_ciphertext[0] ~= 0xa5 @@ -260,7 +227,7 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], derived_ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") aad[0] ~= 0xa5 ok = chacha20poly1305.decrypt( @@ -271,15 +238,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") } @(test) test_rand_bytes :: proc(t: ^testing.T) { - tc.log(t, "Testing rand_bytes") - if !crypto.HAS_RAND_BYTES { - tc.log(t, "rand_bytes not supported - skipping") + log.info("rand_bytes not supported - skipping") return } @@ -307,10 +272,5 @@ test_rand_bytes :: proc(t: ^testing.T) { break } } - - tc.expect( - t, - seems_ok, - "Expected to randomize the head and tail of the buffer within a handful of attempts", - ) + testing.expect(t, seems_ok, "Expected to randomize the head and tail of the buffer within a handful of attempts") } diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index f33292a53..8b96b0ae9 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -2,19 +2,17 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" +import "core:log" import "core:testing" import "core:crypto/aes" import "core:crypto/sha2" -import tc "tests:common" - @(test) test_aes :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - tc.log(t, "Testing AES") + log.info("Testing AES") impls := make([dynamic]aes.Implementation, 0, 2) append(&impls, aes.Implementation.Portable) @@ -29,9 +27,8 @@ test_aes :: proc(t: ^testing.T) { } } -@(test) test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-ECB/%v", impl)) + log.infof("Testing AES-ECB/%v", impl) test_vectors := []struct { key: string, @@ -111,39 +108,34 @@ test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { aes.encrypt_ecb(&ctx, dst[:], plaintext) dst_str := string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext, - fmt.tprintf( - "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", - impl, - v.ciphertext, - v.key, - v.plaintext, - dst_str, - ), + "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.plaintext, + dst_str, ) aes.decrypt_ecb(&ctx, dst[:], ciphertext) dst_str = string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.plaintext, - fmt.tprintf( - "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", - impl, - v.plaintext, - v.key, - v.ciphertext, - dst_str, - ), + "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", + impl, + v.plaintext, + v.key, + v.ciphertext, + dst_str, ) } } -@(test) test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-CTR/%v", impl)) + log.infof("Testing AES-CTR/%v", impl) test_vectors := []struct { key: string, @@ -185,18 +177,16 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { aes.xor_bytes_ctr(&ctx, dst, plaintext) dst_str := string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext, - fmt.tprintf( - "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", - impl, - v.ciphertext, - v.key, - v.iv, - v.plaintext, - dst_str, - ), + "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.iv, + v.plaintext, + dst_str, ) } @@ -224,21 +214,18 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { digest_str := string(hex.encode(digest[:], context.temp_allocator)) expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" - tc.expect( + testing.expectf( t, expected_digest_str == digest_str, - fmt.tprintf( - "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", - impl, - expected_digest_str, - digest_str, - ), + "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + digest_str, ) } -@(test) test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-GCM/%v", impl)) + log.infof("Testing AES-GCM/%v", impl) // NIST did a reorg of their site, so the source of the test vectors // is only available from an archive. The commented out tests are @@ -422,41 +409,37 @@ test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { dst_str := string(hex.encode(dst[:], context.temp_allocator)) tag_str := string(hex.encode(tag_[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext && tag_str == v.tag, - fmt.tprintf( - "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", - impl, - v.ciphertext, - v.tag, - v.key, - v.iv, - v.aad, - v.plaintext, - dst_str, - tag_str, - ), + "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, ) ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag) dst_str = string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, ok && dst_str == v.plaintext, - fmt.tprintf( - "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", - impl, - v.plaintext, - v.key, - v.iv, - v.aad, - v.ciphertext, - v.tag, - dst_str, - ok, - ), + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, ) } } diff --git a/tests/core/crypto/test_core_crypto_ecc25519.odin b/tests/core/crypto/test_core_crypto_ecc25519.odin index 5ea008f90..baf4a1a38 100644 --- a/tests/core/crypto/test_core_crypto_ecc25519.odin +++ b/tests/core/crypto/test_core_crypto_ecc25519.odin @@ -1,8 +1,6 @@ package test_core_crypto -import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" import field "core:crypto/_fiat/field_curve25519" @@ -10,25 +8,8 @@ import "core:crypto/ed25519" import "core:crypto/ristretto255" import "core:crypto/x25519" -import tc "tests:common" - -@(test) -test_ecc25519 :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing curve25519 ECC") - - test_sqrt_ratio_m1(t) - test_ristretto255(t) - - test_ed25519(t) - test_x25519(t) -} - @(test) test_sqrt_ratio_m1 :: proc(t: ^testing.T) { - tc.log(t, "Testing sqrt_ratio_m1") - test_vectors := []struct { u: string, v: string, @@ -90,25 +71,21 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { field.fe_relax_cast(&vee), ) - tc.expect( + testing.expectf( t, (was_square == 1) == v.was_square && field.fe_equal_bytes(&r, r_) == 1, - fmt.tprintf( - "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", - v.was_square, - v.r, - v.u, - v.v, - fe_str(&r), - ), + "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", + v.was_square, + v.r, + v.u, + v.v, + fe_str(&r), ) } } @(test) test_ristretto255 :: proc(t: ^testing.T) { - tc.log(t, "Testing ristretto255") - ge_gen: ristretto255.Group_Element ristretto255.ge_generator(&ge_gen) @@ -158,7 +135,7 @@ test_ristretto255 :: proc(t: ^testing.T) { ge: ristretto255.Group_Element ok := ristretto255.ge_set_bytes(&ge, b) - tc.expect(t, !ok, fmt.tprintf("Expected false for %s", x)) + testing.expectf(t, !ok, "Expected false for %s", x) } generator_multiples := []string { @@ -185,22 +162,20 @@ test_ristretto255 :: proc(t: ^testing.T) { ge := &ges[i] ok := ristretto255.ge_set_bytes(ge, b) - tc.expect(t, ok, fmt.tprintf("Expected true for %s", x)) + testing.expectf(t, ok, "Expected true for %s", x) x_check := ge_str(ge) - tc.expect( + testing.expectf( t, x == x_check, - fmt.tprintf( - "Expected %s (round-trip) but got %s instead", - x, - x_check, - ), + "Expected %s (round-trip) but got %s instead", + x, + x_check, ) if i == 1 { - tc.expect( + testing.expect( t, ristretto255.ge_equal(ge, &ge_gen) == 1, "Expected element 1 to be the generator", @@ -217,41 +192,35 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_scalarmult_generator(&ge_check, &sc) x_check := ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (specialized), got %s", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (specialized), got %s", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult_vartime(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic vartime), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic vartime), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) switch i { @@ -261,28 +230,24 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_add(&ge_check, ge_prev, &ge_gen) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) - tc.expect( + testing.expectf( t, ristretto255.ge_equal(&ges[i], &ge_check) == 1, - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) } } @@ -344,22 +309,18 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_set_wide_bytes(&ge, in_bytes) ge_check := ge_str(&ge) - tc.expect( + testing.expectf( t, ge_check == v.output, - fmt.tprintf( - "Expected %s for %s, got %s", - v.output, - ge_check, - ), + "Expected %s for %s, got %s", + v.output, + ge_check, ) } } @(test) test_ed25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing ed25519") - test_vectors_rfc := []struct { priv_key: string, pub_key: string, @@ -401,87 +362,73 @@ test_ed25519 :: proc(t: ^testing.T) { priv_key: ed25519.Private_Key ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid private key", - v.priv_key, - ), + "Expected %s to be a valid private key", + v.priv_key, ) key_bytes: [32]byte ed25519.private_key_bytes(&priv_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected private key %s round-trip, got %s", - v.priv_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected private key %s round-trip, got %s", + v.priv_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) pub_key: ed25519.Public_Key ok = ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid public key (priv->pub: %s)", - v.pub_key, - string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), - ), + "Expected %s to be a valid public key (priv->pub: %s)", + v.pub_key, + string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), ) ed25519.public_key_bytes(&pub_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected public key %s round-trip, got %s", - v.pub_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected public key %s round-trip, got %s", + v.pub_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) sig: [ed25519.SIGNATURE_SIZE]byte ed25519.sign(&priv_key, msg_bytes, sig[:]) x := string(hex.encode(sig[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, x == v.sig, - fmt.tprintf( - "Expected %s for sign(%s, %s), got %s", - v.sig, - v.priv_key, - v.msg, - x, - ), + "Expected %s for sign(%s, %s), got %s", + v.sig, + v.priv_key, + v.msg, + x, ) ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(%s, %s, %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected true for verify(%s, %s, %s)", + v.pub_key, + v.msg, + v.sig, ) ok = ed25519.verify(&priv_key._pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(pub(%s), %s %s)", - v.priv_key, - v.msg, - v.sig, - ), + "Expected true for verify(pub(%s), %s %s)", + v.priv_key, + v.msg, + v.sig, ) // Corrupt the message and make sure verification fails. @@ -493,15 +440,13 @@ test_ed25519 :: proc(t: ^testing.T) { msg_bytes[0] = msg_bytes[0] ~ 69 } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == false, - fmt.tprintf( - "Expected false for verify(%s, %s (corrupted), %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected false for verify(%s, %s (corrupted), %s)", + v.pub_key, + v.msg, + v.sig, ) } @@ -634,15 +579,13 @@ test_ed25519 :: proc(t: ^testing.T) { pub_key: ed25519.Public_Key ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok == v.pub_key_ok, - fmt.tprintf( - "speccheck/%d: Expected %s to be a (in)valid public key, got %v", - i, - v.pub_key, - ok, - ), + "speccheck/%d: Expected %s to be a (in)valid public key, got %v", + i, + v.pub_key, + ok, ) // If A is rejected for being non-canonical, skip signature check. @@ -651,17 +594,15 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == v.sig_ok, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s)", - i, - v.sig_ok, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s)", + i, + v.sig_ok, + v.pub_key, + v.msg, + v.sig, ) // If the signature is accepted, skip the relaxed signature check. @@ -670,25 +611,21 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes, true) - tc.expect( + testing.expectf( t, ok == v.sig_ok_relaxed, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s, true)", - i, - v.sig_ok_relaxed, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s, true)", + i, + v.sig_ok_relaxed, + v.pub_key, + v.msg, + v.sig, ) } } @(test) test_x25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing X25519") - // Local copy of this so that the base point doesn't need to be exported. _BASE_POINT: [32]byte = { 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -720,17 +657,15 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(derived_point[:], scalar[:], point[:]) derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_point_str == v.product, - fmt.tprintf( - "Expected %s for %s * %s, but got %s instead", - v.product, - v.scalar, - v.point, - derived_point_str, - ), - ) + "Expected %s for %s * %s, but got %s instead", + v.product, + v.scalar, + v.point, + derived_point_str, + ) // Abuse the test vectors to sanity-check the scalar-basepoint multiply. p1, p2: [x25519.POINT_SIZE]byte @@ -738,15 +673,13 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:]) p1_str := string(hex.encode(p1[:], context.temp_allocator)) p2_str := string(hex.encode(p2[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, p1_str == p2_str, - fmt.tprintf( - "Expected %s for %s * basepoint, but got %s instead", - p2_str, - v.scalar, - p1_str, - ), + "Expected %s for %s * basepoint, but got %s instead", + p2_str, + v.scalar, + p1_str, ) } } @@ -763,4 +696,4 @@ fe_str :: proc(fe: ^field.Tight_Field_Element) -> string { b: [32]byte field.fe_to_bytes(&b, fe) return string(hex.encode(b[:], context.temp_allocator)) -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_hash.odin b/tests/core/crypto/test_core_crypto_hash.odin index c4e8e8dd7..9a9d0cc76 100644 --- a/tests/core/crypto/test_core_crypto_hash.odin +++ b/tests/core/crypto/test_core_crypto_hash.odin @@ -3,23 +3,17 @@ package test_core_crypto import "base:runtime" import "core:bytes" import "core:encoding/hex" -import "core:fmt" import "core:strings" import "core:testing" - import "core:crypto/hash" -import tc "tests:common" - @(test) test_hash :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - tc.log(t, "Testing Hashes") - // TODO: // - Stick the test vectors in a JSON file or something. - data_1_000_000_a := strings.repeat("a", 1_000_000) + data_1_000_000_a := strings.repeat("a", 1_000_000, context.temp_allocator) digest: [hash.MAX_DIGEST_SIZE]byte test_vectors := []struct{ @@ -496,16 +490,14 @@ test_hash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.hash, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s, but got %s instead", - algo_name, - v.hash, - v.str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s, but got %s instead", + algo_name, + v.hash, + v.str, + dst_str, ) } @@ -521,25 +513,21 @@ test_hash :: proc(t: ^testing.T) { // still correct. digest_sz := hash.DIGEST_SIZES[algo] block_sz := hash.BLOCK_SIZES[algo] - tc.expect( + testing.expectf( t, digest_sz <= hash.MAX_DIGEST_SIZE, - fmt.tprintf( - "%s: Digest size %d exceeds max %d", - algo_name, - digest_sz, - hash.MAX_DIGEST_SIZE, - ), + "%s: Digest size %d exceeds max %d", + algo_name, + digest_sz, + hash.MAX_DIGEST_SIZE, ) - tc.expect( + testing.expectf( t, block_sz <= hash.MAX_BLOCK_SIZE, - fmt.tprintf( - "%s: Block size %d exceeds max %d", - algo_name, - block_sz, - hash.MAX_BLOCK_SIZE, - ), + "%s: Block size %d exceeds max %d", + algo_name, + block_sz, + hash.MAX_BLOCK_SIZE, ) // Exercise most of the happy-path for the high level interface. @@ -553,15 +541,13 @@ test_hash :: proc(t: ^testing.T) { a_str := string(hex.encode(digest_a, context.temp_allocator)) b_str := string(hex.encode(digest_b, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str, - fmt.tprintf( - "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", - algo_name, - a_str, - b_str, - ), + "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", + algo_name, + a_str, + b_str, ) // Exercise the rolling digest functionality, which also covers @@ -571,25 +557,21 @@ test_hash :: proc(t: ^testing.T) { api_algo := hash.algorithm(&ctx) api_digest_size := hash.digest_size(&ctx) - tc.expect( + testing.expectf( t, algo == api_algo, - fmt.tprintf( - "%s/algorithm: Expected: %v but got %v instead", - algo_name, - algo, - api_algo, - ), + "%s/algorithm: Expected: %v but got %v instead", + algo_name, + algo, + api_algo, ) - tc.expect( + testing.expectf( t, hash.DIGEST_SIZES[algo] == api_digest_size, - fmt.tprintf( - "%s/digest_size: Expected: %d but got %d instead", - algo_name, - hash.DIGEST_SIZES[algo], - api_digest_size, - ), + "%s/digest_size: Expected: %d but got %d instead", + algo_name, + hash.DIGEST_SIZES[algo], + api_digest_size, ) hash.update(&ctx, digest_a) @@ -604,16 +586,14 @@ test_hash :: proc(t: ^testing.T) { b_str = string(hex.encode(digest_b, context.temp_allocator)) c_str := string(hex.encode(digest_c, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str && b_str == c_str, - fmt.tprintf( - "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", - algo_name, - a_str, - b_str, - c_str, - ), + "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", + algo_name, + a_str, + b_str, + c_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_kdf.odin b/tests/core/crypto/test_core_crypto_kdf.odin index 73177d8be..247529e65 100644 --- a/tests/core/crypto/test_core_crypto_kdf.odin +++ b/tests/core/crypto/test_core_crypto_kdf.odin @@ -2,28 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/hash" import "core:crypto/hkdf" import "core:crypto/pbkdf2" -import tc "tests:common" - -@(test) -test_kdf :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing KDFs") - - test_hkdf(t) - test_pbkdf2(t) -} - @(test) test_hkdf :: proc(t: ^testing.T) { - tc.log(t, "Testing HKDF") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [128]byte // Good enough. @@ -70,25 +56,23 @@ test_hkdf :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.okm, - fmt.tprintf( - "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", - algo_name, - v.okm, - v.ikm, - v.salt, - v.info, - dst_str, - ), + "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", + algo_name, + v.okm, + v.ikm, + v.salt, + v.info, + dst_str, ) } } @(test) test_pbkdf2 :: proc(t: ^testing.T) { - tc.log(t, "Testing PBKDF2") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [64]byte // 512-bits is enough for every output for now. @@ -174,18 +158,16 @@ test_pbkdf2 :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.dk, - fmt.tprintf( - "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", - algo_name, - v.dk, - v.password, - v.salt, - v.iterations, - dst_str, - ), + "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", + algo_name, + v.dk, + v.password, + v.salt, + v.iterations, + dst_str, ) } } diff --git a/tests/core/crypto/test_core_crypto_mac.odin b/tests/core/crypto/test_core_crypto_mac.odin index f2eeacb19..ed95ba0ad 100644 --- a/tests/core/crypto/test_core_crypto_mac.odin +++ b/tests/core/crypto/test_core_crypto_mac.odin @@ -2,30 +2,17 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" - import "core:crypto/hash" import "core:crypto/hmac" import "core:crypto/poly1305" import "core:crypto/siphash" -import tc "tests:common" - -@(test) -test_mac :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing MACs") - - test_hmac(t) - test_poly1305(t) - test_siphash_2_4(t) -} - @(test) test_hmac :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Test cases pulled out of RFC 6234, note that HMAC is a generic // construct so as long as the underlying hash is correct and all // the code paths are covered the implementation is "fine", so @@ -86,40 +73,36 @@ test_hmac :: proc(t: ^testing.T) { msg_str := string(hex.encode(msg, context.temp_allocator)) dst_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == expected_str, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + dst_str, ) hmac.sum(algo, dst, msg, key) oneshot_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, oneshot_str == expected_str, - fmt.tprintf( - "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - oneshot_str, - ), + "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + oneshot_str, ) } } @(test) test_poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing poly1305") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from poly1305-donna. key := [poly1305.KEY_SIZE]byte { @@ -157,16 +140,17 @@ test_poly1305 :: proc(t: ^testing.T) { // Verify - oneshot + compare ok := poly1305.verify(tag[:], msg[:], key[:]) - tc.expect(t, ok, "oneshot verify call failed") + testing.expect(t, ok, "oneshot verify call failed") // Sum - oneshot derived_tag: [poly1305.TAG_SIZE]byte poly1305.sum(derived_tag[:], msg[:], key[:]) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str), + "Expected %s for sum(msg, key), but got %s instead", + tag_str, derived_tag_str, ) // Incremental @@ -182,21 +166,16 @@ test_poly1305 :: proc(t: ^testing.T) { } poly1305.final(&ctx, derived_tag[:]) derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected %s for init/update/final - incremental, but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected %s for init/update/final - incremental, but got %s instead", + tag_str, derived_tag_str, ) } @(test) test_siphash_2_4 :: proc(t: ^testing.T) { - tc.log(t, "Testing SipHash-2-4") - // Test vectors from // https://github.com/veorq/SipHash/blob/master/vectors.h test_vectors := [?]u64 { @@ -225,6 +204,7 @@ test_siphash_2_4 :: proc(t: ^testing.T) { for i in 0 ..< len(test_vectors) { data := make([]byte, i) + defer delete(data) for j in 0 ..< i { data[j] = byte(j) } @@ -232,15 +212,13 @@ test_siphash_2_4 :: proc(t: ^testing.T) { vector := test_vectors[i] computed := siphash.sum_2_4(data[:], key[:]) - tc.expect( + testing.expectf( t, computed == vector, - fmt.tprintf( - "Expected: 0x%x for input of %v, but got 0x%x instead", - vector, - data, - computed, - ), + "Expected: 0x%x for input of %v, but got 0x%x instead", + vector, + data, + computed, ) } } diff --git a/tests/core/crypto/test_core_crypto_sha3_variants.odin b/tests/core/crypto/test_core_crypto_sha3_variants.odin index 8e44996bc..c11868e72 100644 --- a/tests/core/crypto/test_core_crypto_sha3_variants.odin +++ b/tests/core/crypto/test_core_crypto_sha3_variants.odin @@ -2,30 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/kmac" import "core:crypto/shake" import "core:crypto/tuplehash" -import tc "tests:common" - -@(test) -test_sha3_variants :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing SHA3 derived functions") - - test_shake(t) - test_cshake(t) - test_tuplehash(t) - test_kmac(t) -} - @(test) test_shake :: proc(t: ^testing.T) { - tc.log(t, "Testing SHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -67,23 +51,21 @@ test_shake :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "SHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "SHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_cshake :: proc(t: ^testing.T) { - tc.log(t, "Testing cSHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -135,29 +117,27 @@ test_cshake :: proc(t: ^testing.T) { shake.init_cshake_256(&ctx, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.str)) + data, _ := hex.decode(transmute([]byte)(v.str), context.temp_allocator) shake.write(&ctx, data) shake.read(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "cSHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "cSHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_tuplehash :: proc(t: ^testing.T) { - tc.log(t, "Testing TupleHash(XOF)") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -317,7 +297,7 @@ test_tuplehash :: proc(t: ^testing.T) { } for e in v.tuple { - data, _ := hex.decode(transmute([]byte)(e)) + data, _ := hex.decode(transmute([]byte)(e), context.temp_allocator) tuplehash.write_element(&ctx, data) } @@ -332,24 +312,22 @@ test_tuplehash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", - suffix, - v.sec_strength, - v.output, - v.tuple, - dst_str, - ), + "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", + suffix, + v.sec_strength, + v.output, + v.tuple, + dst_str, ) } } @(test) test_kmac :: proc(t:^testing.T) { - tc.log(t, "Testing KMAC") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -410,7 +388,7 @@ test_kmac :: proc(t:^testing.T) { for v in test_vectors { dst := make([]byte, len(v.output) / 2, context.temp_allocator) - key, _ := hex.decode(transmute([]byte)(v.key)) + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) domainsep := transmute([]byte)(v.domainsep) ctx: kmac.Context @@ -421,24 +399,22 @@ test_kmac :: proc(t:^testing.T) { kmac.init_256(&ctx, key, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.msg)) + data, _ := hex.decode(transmute([]byte)(v.msg), context.temp_allocator) kmac.update(&ctx, data) kmac.final(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", - v.sec_strength, - v.output, - v.key, - v.domainsep, - v.msg, - dst_str, - ), + "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", + v.sec_strength, + v.output, + v.key, + v.domainsep, + v.msg, + dst_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin index 600dc9ade..5bfc579bd 100644 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ b/tests/core/crypto/test_crypto_benchmark.odin @@ -2,7 +2,7 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" +import "core:log" import "core:testing" import "core:time" @@ -13,16 +13,11 @@ import "core:crypto/ed25519" import "core:crypto/poly1305" import "core:crypto/x25519" -import tc "tests:common" - // Cryptographic primitive benchmarks. @(test) bench_crypto :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - fmt.println("Starting benchmarks:") - bench_chacha20(t) bench_poly1305(t) bench_chacha20poly1305(t) @@ -157,8 +152,8 @@ _benchmark_aes256_gcm :: proc( } benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf( - "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + log.infof( + "\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", name, options.rounds, options.processed, @@ -179,19 +174,19 @@ bench_chacha20 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "ChaCha20 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "ChaCha20 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -206,13 +201,13 @@ bench_poly1305 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "Poly1305 1024 zero bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -227,19 +222,19 @@ bench_chacha20poly1305 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "chacha20poly1305 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "chacha20poly1305 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -265,19 +260,19 @@ bench_aes256_gcm :: proc(t: ^testing.T) { context.user_ptr = &ctx err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "AES256-GCM 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "AES256-GCM 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -293,12 +288,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "private key should deserialize") } elapsed := time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.private_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), + log.infof( + "ed25519.private_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" @@ -309,12 +301,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "public key should deserialize") } elapsed = time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.public_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), + log.infof( + "ed25519.public_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) msg := "Got a job for you, 621." @@ -325,7 +314,10 @@ bench_ed25519 :: proc(t: ^testing.T) { ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) } elapsed = time.since(start) - tc.log(t, fmt.tprintf("ed25519.sign: ~%f us/op", time.duration_microseconds(elapsed) / iters)) + log.infof( + "ed25519.sign: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) start = time.now() for i := 0; i < iters; i = i + 1 { @@ -333,9 +325,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "signature should validate") } elapsed = time.since(start) - tc.log( - t, - fmt.tprintf("ed25519.verify: ~%f us/op", time.duration_microseconds(elapsed) / iters), + log.infof( + "ed25519.verify: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) } @@ -354,8 +346,8 @@ bench_x25519 :: proc(t: ^testing.T) { } elapsed := time.since(start) - tc.log( - t, - fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters), + log.infof( + "x25519.scalarmult: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) } diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index e3bef6c9d..7fc04ce6b 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -23,9 +23,8 @@ import "core:hash" import "core:strings" import "core:mem" import "core:time" -import "base:runtime" -TEST_SUITE_PATH :: "assets/PNG" +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/" I_Error :: image.Error @@ -1455,10 +1454,9 @@ png_test_no_postproc :: proc(t: ^testing.T) { } run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { - context = runtime.default_context() - for file in suite { - test_file := strings.concatenate({TEST_SUITE_PATH, "/", file.file, ".png"}, context.temp_allocator) + test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"}) + defer delete(test_file) img: ^png.Image err: png.Error @@ -1467,10 +1465,6 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { for test in file.tests { count += 1 - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - img, err = png.load(test_file, test.options) passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) From 568b746c9897ea4b343d454309d6d1275a4a5320 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 16:39:20 -0400 Subject: [PATCH 029/151] Fix indentation --- core/mem/rollback_stack_allocator.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index bf397d2c8..6fa86ab0b 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -292,7 +292,7 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo } case .Free: - err = rb_free(stack, old_memory) + err = rb_free(stack, old_memory) case .Free_All: rb_free_all(stack) From 0f675fa4368253e8ebdf9dad325bbba2101ecd22 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:36:20 -0400 Subject: [PATCH 030/151] Use `uintptr` where applicable in `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 6fa86ab0b..104ad0e95 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -38,19 +38,19 @@ ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte // // This is because allocations over the block size are not split up if the item // within is freed; they are immediately returned to the block allocator. -ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 1 * Gigabyte +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte Rollback_Stack_Header :: bit_field u64 { - prev_offset: int | 32, - is_free: bool | 1, - prev_ptr: int | 31, + prev_offset: uintptr | 32, + is_free: bool | 1, + prev_ptr: uintptr | 31, } Rollback_Stack_Block :: struct { next_block: ^Rollback_Stack_Block, last_alloc: rawptr, - offset: int, + offset: uintptr, buffer: []byte, } @@ -65,7 +65,7 @@ Rollback_Stack :: struct { @(require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := cast(uintptr)raw_data(block.buffer) - end := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + end := cast(uintptr)raw_data(block.buffer) + block.offset return start < cast(uintptr)ptr && cast(uintptr)ptr <= end } @@ -150,8 +150,8 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { - if block.offset + (size - old_size) < len(block.buffer) { - block.offset += (size - old_size) + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { + block.offset += cast(uintptr)size - cast(uintptr)old_size #no_bounds_check return (cast([^]byte)ptr)[:size], nil } } @@ -180,10 +180,10 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt parent.next_block = block } - start := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset - padding := calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + start := cast(uintptr)raw_data(block.buffer) + block.offset + padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) - if block.offset + padding + size > len(block.buffer) { + if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { parent = block continue } @@ -193,17 +193,17 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt header^ = { prev_offset = block.offset, - prev_ptr = max(0, cast(int)(cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer))), + prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer), is_free = false, } block.last_alloc = ptr - block.offset += padding + size + block.offset += padding + cast(uintptr)size if len(block.buffer) > stack.block_size { // This block exceeds the allocator's standard block size and is considered a singleton. // Prevent any further allocations on it. - block.offset = len(block.buffer) + block.offset = cast(uintptr)len(block.buffer) } #no_bounds_check return ptr[:size], nil @@ -242,7 +242,7 @@ rollback_stack_init_dynamic :: proc( block_allocator := context.allocator, ) -> Allocator_Error { assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 1 gigabyte.") + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.") block := rb_make_block(block_size, block_allocator) or_return From 09ef08f0354cd68ca589e3c6d094f08d08a48d78 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 18:23:05 -0400 Subject: [PATCH 031/151] Add more sanity checking to `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 104ad0e95..a3f6647cf 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -150,6 +150,10 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + // `block.offset` should never underflow because it is contingent + // on `old_size` in the first place, assuming sane arguments. + assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { block.offset += cast(uintptr)size - cast(uintptr)old_size #no_bounds_check return (cast([^]byte)ptr)[:size], nil @@ -169,6 +173,10 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { + when !ODIN_DISABLE_ASSERT { + allocated_new_block: bool + } + if block == nil { if stack.block_allocator.procedure == nil { return nil, .Out_Of_Memory @@ -178,12 +186,20 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt new_block_size := max(minimum_size_required, stack.block_size) block = rb_make_block(new_block_size, stack.block_allocator) or_return parent.next_block = block + when !ODIN_DISABLE_ASSERT { + allocated_new_block = true + } } start := cast(uintptr)raw_data(block.buffer) + block.offset padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { + when !ODIN_DISABLE_ASSERT { + if allocated_new_block { + panic("Rollback Stack Allocator allocated a new block but did not use it.") + } + } parent = block continue } @@ -223,9 +239,9 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac } -rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte) { +rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) - assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.") + assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) block := cast(^Rollback_Stack_Block)raw_data(buffer) block^ = {} @@ -240,9 +256,10 @@ rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_allocator := context.allocator, + location := #caller_location, ) -> Allocator_Error { - assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.") + assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location) + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) block := rb_make_block(block_size, block_allocator) or_return @@ -284,7 +301,8 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo switch mode { case .Alloc, .Alloc_Non_Zeroed: - assert(is_power_of_two(cast(uintptr)alignment), "alignment must be a power of two", location) + assert(size >= 0, "Size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) result = rb_alloc(stack, size, alignment) or_return if mode == .Alloc { @@ -298,6 +316,9 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo rb_free_all(stack) case .Resize, .Resize_Non_Zeroed: + assert(size >= 0, "Size must be positive or zero.", location) + assert(old_size >= 0, "Old size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) result = rb_resize(stack, old_memory, old_size, size, alignment) or_return #no_bounds_check if mode == .Resize && size > old_size { From 1afc235359334aa2b7df0e4b4c79386763076e75 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:14:28 -0400 Subject: [PATCH 032/151] Use plain sort for `internal_tests` --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ce5aa112a..1a9fc3ddc 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -192,7 +192,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(it.p != nil, "Test %s.%s has procedure.", it.pkg, it.name) } - slice.stable_sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { + slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { if a.pkg == b.pkg { return a.name < b.name } else { From eadfbb13185a94a30d87c52eb50e83fcec8e0d52 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:49:56 -0400 Subject: [PATCH 033/151] Forbid singleton allocations from shrinking their block offset --- core/mem/rollback_stack_allocator.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index a3f6647cf..b86f514ec 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -155,7 +155,11 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { - block.offset += cast(uintptr)size - cast(uintptr)old_size + // Prevent singleton allocations from fragmenting by forbidding + // them to shrink, removing the possibility of overflow bugs. + if len(block.buffer) <= stack.block_size { + block.offset += cast(uintptr)size - cast(uintptr)old_size + } #no_bounds_check return (cast([^]byte)ptr)[:size], nil } } From dffc3af86c1c101e1a7c465478ae5454f811fb89 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:55:45 -0400 Subject: [PATCH 034/151] Remove `safe_heap_allocator` from test runner I was under the impression that the default `context.allocator` was not thread-safe, but I've been told that this is not the case. --- core/testing/runner.odin | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 1a9fc3ddc..3a024d090 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -277,10 +277,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error) defer delete(task_data_slots) - safe_heap: mem.Mutex_Allocator - mem.mutex_allocator_init(&safe_heap, context.allocator) - safe_heap_allocator := mem.mutex_allocator(&safe_heap) - // Tests rotate through these allocators as they finish. task_allocators: []mem.Rollback_Stack = --- task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count) @@ -295,7 +291,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } #no_bounds_check for i in 0 ..< thread_count { - alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY, block_allocator = safe_heap_allocator) + alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY) fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error) when TRACKING_MEMORY { mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i])) @@ -339,11 +335,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // NOTE(Feoramund): This is the allocator that will be used by threads to // persist log messages past their lifetimes. It has its own variable name - // in the event it needs to be changed from `safe_heap_allocator` without + // in the event it needs to be changed from `context.allocator` without // digging through the source to divine everywhere it is used for that. - shared_log_allocator := safe_heap_allocator - - context.allocator = safe_heap_allocator + shared_log_allocator := context.allocator context.logger = { procedure = runner_logger_proc, From 89d8df28bec7b5aebaac1e38ef642754ef57e1f1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:58:35 -0400 Subject: [PATCH 035/151] Combine multi-line attributes onto one line --- core/mem/rollback_stack_allocator.odin | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index b86f514ec..75b3cd745 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -61,16 +61,14 @@ Rollback_Stack :: struct { } -@(private="file") -@(require_results) +@(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := cast(uintptr)raw_data(block.buffer) end := cast(uintptr)raw_data(block.buffer) + block.offset return start < cast(uintptr)ptr && cast(uintptr)ptr <= end } -@(private="file") -@(require_results) +@(private="file", require_results) rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( parent: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block, @@ -87,8 +85,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, nil, .Invalid_Pointer } -@(private="file") -@(require_results) +@(private="file", require_results) rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header, @@ -113,8 +110,7 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ } } -@(private="file") -@(require_results) +@(private="file", require_results) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return if header.is_free { @@ -145,8 +141,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.offset = 0 } -@(private="file") -@(require_results) +@(private="file", require_results) rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { @@ -172,8 +167,7 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment return } -@(private="file") -@(require_results) +@(private="file", require_results) rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { @@ -232,8 +226,7 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt return nil, .Out_Of_Memory } -@(private="file") -@(require_results) +@(private="file", require_results) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return From a1c5bebac72d14711c317e393d337143df17ca87 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:36:22 -0400 Subject: [PATCH 036/151] Fix ANSI redraw eating last log line --- core/testing/reporting.odin | 6 +++++- core/testing/runner.odin | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index d06681c2d..fba67d67f 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -271,7 +271,11 @@ needs_to_redraw :: proc(report: Report) -> bool { } draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) { - if total_done_count != total_test_count { + if total_done_count == total_test_count { + // All tests are done; print a blank line to maintain the same height + // of the progress report. + fmt.wprintln(w) + } else { fmt.wprintfln(w, "%s % 4i/% 4i :: total", threads_string, diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 3a024d090..734b143a6 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -583,8 +583,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { finished_in := time.since(start_time) + when !FANCY_OUTPUT { + // One line to space out the results, since we don't have the status + // bar in plain mode. + fmt.wprintln(batch_writer) + } + fmt.wprintf(batch_writer, - "\nFinished %i test%s in %v.", + "Finished %i test%s in %v.", total_done_count, "" if total_done_count == 1 else "s", finished_in) From dcfda195d2c1e215dd506253703fc04e660c9fc6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:36:50 -0400 Subject: [PATCH 037/151] Send terminal control code to `STDOUT` instead `STDERR` might be redirected, and this code signals to the terminal to show the cursor again. Otherwise, the cursor will be invisible. --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 734b143a6..3b4e59e48 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -667,7 +667,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - fmt.wprint(batch_writer, ansi.CSI + ansi.DECTCEM_SHOW) + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) From e11f3d252037500197672db990e8884faee230e4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:47:01 -0400 Subject: [PATCH 038/151] Fix missing `-` for define in `tests/core/build.bat` --- tests/core/build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index fc5e2dc3f..29603dc68 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -11,7 +11,7 @@ echo --- echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% define:test_progress_width=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -define:test_progress_width=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests From b7e1ae7073a96099c9a5b18fa6fd44a561823691 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:50:16 -0400 Subject: [PATCH 039/151] Change test runner options to `SCREAMING_SNAKE_CASE` This commit also changes the name of `test_select` to `ODIN_TEST_NAMES`, to better conform with the already-existing `-test-name:` option. --- core/testing/runner.odin | 22 +++++++++++----------- tests/core/Makefile | 8 ++++---- tests/core/build.bat | 12 ++++++------ tests/core/image/build.bat | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 3b4e59e48..c79d5537b 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -23,19 +23,19 @@ _ :: pkg_log _ :: strings // Specify how many threads to use when running tests. -TEST_THREADS : int : #config(test_threads, 0) +TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(test_track_memory, false) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, false) // Specify how much memory each thread allocator starts with. -PER_THREAD_MEMORY : int : #config(test_thread_memory, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. -TEST_SELECT : string : #config(test_select, "") +TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. -FANCY_OUTPUT : bool : #config(test_fancy, true) +FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. -USE_CLIPBOARD : bool : #config(test_clipboard, false) +USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. -PROGRESS_WIDTH : int : #config(test_progress_width, 24) +PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) end_t :: proc(t: ^T) { @@ -122,12 +122,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { alloc_error: mem.Allocator_Error - when TEST_SELECT != "" { + when TEST_NAMES != "" { select_internal_tests: [dynamic]Internal_Test defer delete(select_internal_tests) { - index_list := TEST_SELECT + index_list := TEST_NAMES for selector in strings.split_iterator(&index_list, ",") { // Temp allocator is fine since we just need to identify which test it's referring to. split_selector := strings.split(selector, ".", context.temp_allocator) @@ -640,7 +640,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if total_success_count > 0 { when USE_CLIPBOARD { clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer)) - fmt.wprint(clipboard_writer, "-define:test_select=") + fmt.wprint(clipboard_writer, "-define:ODIN_TEST_NAMES=") for test_index in sorted_failed_test_reasons { #no_bounds_check it := internal_tests[test_index] fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) @@ -655,7 +655,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { "" if total_failure_count == 1 else "s", " has" if total_failure_count == 1 else "s have") } else { - fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:test_select=", + fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:ODIN_TEST_NAMES=", "" if total_failure_count == 1 else "s") for test_index in sorted_failed_test_reasons { #no_bounds_check it := internal_tests[test_index] diff --git a/tests/core/Makefile b/tests/core/Makefile index 4dede0370..13f8ef41b 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true COLLECTION=-collection:tests=.. all: all_bsd \ @@ -34,13 +34,13 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image + $(ODIN) test image $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image compress_test: - $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress + $(ODIN) test compress $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress container_test: - $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container + $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container crypto_test: $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto diff --git a/tests/core/build.bat b/tests/core/build.bat index 29603dc68..b63c0f311 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,17 +1,17 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% test compress %COMMON% -define:test_progress_width=3 -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% -define:test_progress_width=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests @@ -25,7 +25,7 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:test_threads=1 -define:test_fancy=false || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:ODIN_TEST_THREADS=1 -define:ODIN_TEST_FANCY=false || exit /b %PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% test image %COMMON% -define:test_progress_width=12 -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image.exe || exit /b echo --- echo Running core:math tests @@ -107,4 +107,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat index 35d1c64e9..5a07971b8 100644 --- a/tests/core/image/build.bat +++ b/tests/core/image/build.bat @@ -1,2 +1,2 @@ @echo off -odin test . -define:test_track_memory=true -define:test_progress_width=12 -vet -strict-style \ No newline at end of file +odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style From c531427ee5d95187d4da1edb1679b67af58b4cbb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 22:18:05 +0200 Subject: [PATCH 040/151] Update -define for `crypto` --- tests/core/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 13f8ef41b..23d0dcf99 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -43,7 +43,7 @@ container_test: $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container crypto_test: - $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto + $(ODIN) test crypto $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=18 -o:speed -out:test_crypto strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings From bf42e39b1ce77da0bc45f64ff35322e618a59b9c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:19:06 -0400 Subject: [PATCH 041/151] Be specific about `int` size for `Rollback_Stack` asserts This should fix tests failing on 32-bit platforms. --- core/mem/rollback_stack_allocator.odin | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 75b3cd745..d159897cd 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -251,12 +251,16 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, - block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, + block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_allocator := context.allocator, location := #caller_location, ) -> Allocator_Error { assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location) - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) + when size_of(int) > 4 { + // It's impossible to specify an argument in excess when your integer + // size is insufficient; check only on platforms with big enough ints. + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) + } block := rb_make_block(block_size, block_allocator) or_return From e1a3c0e21d2683804cdfc7644e55ee6ba7a9f0ea Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:39:44 -0400 Subject: [PATCH 042/151] Track memory in the test runner by default --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index c79d5537b..6a9bd6d8f 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -25,7 +25,7 @@ _ :: strings // Specify how many threads to use when running tests. TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, false) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) // Specify how much memory each thread allocator starts with. PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. From 49fa66370f2c96e9fc738ccda67bd3b6d12bad71 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:55:04 -0400 Subject: [PATCH 043/151] Report test memory usage only if there's an issue Adds new option `ODIN_TEST_ALWAYS_REPORT_MEMORY`, for when you always want to see the memory usage report. --- core/testing/runner.odin | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 6a9bd6d8f..759b23b0f 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -23,19 +23,21 @@ _ :: pkg_log _ :: strings // Specify how many threads to use when running tests. -TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) +TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) +// Always report how much memory is used, even when there are no leaks or bad frees. +ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false) // Specify how much memory each thread allocator starts with. -PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. -TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") +TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. -FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) +FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. -USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) +USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. -PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) +PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) end_t :: proc(t: ^T) { @@ -382,8 +384,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } when TRACKING_MEMORY { - pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + when ALWAYS_REPORT_MEMORY { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + } else { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + } pkg_log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } else when ALWAYS_REPORT_MEMORY { + pkg_log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TRACK_MEMORY is false.") } start_time := time.now() @@ -397,10 +405,18 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { when TRACKING_MEMORY { #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] - write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + when ALWAYS_REPORT_MEMORY { + should_report := true + } else { + should_report := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + } - pkg_log.info(bytes.buffer_to_string(&batch_buffer)) - bytes.buffer_reset(&batch_buffer) + if should_report { + write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + + pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + } mem.tracking_allocator_reset(tracker) } From 84ad71fdb3e7714d22cf724c8a593f98100962b1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 17:27:08 -0400 Subject: [PATCH 044/151] Support `ODIN_TEST_PROGRESS_WIDTH=0` This will automatically calculate how wide the progress bars should be based on the package with the greatest number of tests. The progress width is now capped to 100. --- core/testing/reporting.odin | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index fba67d67f..92e144ccc 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -16,9 +16,11 @@ SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN + ansi.SGR SGR_FAILED :: ansi.CSI + ansi.FG_RED + ansi.SGR +MAX_PROGRESS_WIDTH :: 100 + // More than enough bytes to cover long package names, long test names, dozens // of ANSI codes, et cetera. -LINE_BUFFER_SIZE :: (PROGRESS_WIDTH * 8 + 256) * runtime.Byte +LINE_BUFFER_SIZE :: (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.Byte PROGRESS_COLUMN_SPACING :: 2 @@ -44,6 +46,7 @@ Report :: struct { pkg_column_len: int, test_column_len: int, + progress_width: int, all_tests: []Internal_Test, all_test_states: []Test_State, @@ -73,6 +76,10 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: }) or_return } + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, index - pkg_start) + } + pkg_start = index report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg)) cur_pkg = it.pkg @@ -80,7 +87,7 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: report.test_column_len = max(report.test_column_len, len(it.name)) } - // Handle the last package. + // Handle the last (or only) package. #no_bounds_check { append(&packages, Package_Run { name = cur_pkg, @@ -89,6 +96,12 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: test_states = report.all_test_states[pkg_start:], }) or_return } + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, len(internal_tests) - pkg_start) + } else { + report.progress_width = PROGRESS_WIDTH + } + report.progress_width = min(report.progress_width, MAX_PROGRESS_WIDTH) report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg)) @@ -123,7 +136,7 @@ destroy_report :: proc(report: ^Report) { delete(report.all_test_states) } -redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { +redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run) { if pkg.frame_ready { io.write_string(w, pkg.redraw_string) return @@ -150,8 +163,8 @@ redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { } } - start := max(0, highest_run_index - (PROGRESS_WIDTH - 1)) - end := min(start + PROGRESS_WIDTH, len(pkg.test_states)) + start := max(0, highest_run_index - (report.progress_width - 1)) + end := min(start + report.progress_width, len(pkg.test_states)) // This variable is to keep track of the last ANSI code emitted, in // order to avoid repeating the same code over in a sequence. @@ -187,7 +200,7 @@ redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { io.write_byte(line_writer, '|') } - for _ in 0 ..< PROGRESS_WIDTH - (end - start) { + for _ in 0 ..< report.progress_width - (end - start) { io.write_byte(line_writer, ' ') } @@ -255,7 +268,7 @@ redraw_report :: proc(w: io.Writer, report: Report) { // still break... fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF) for &pkg in report.packages { - redraw_package(w, &pkg) + redraw_package(w, report, &pkg) } fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON) } From 6a1649d8aa000e903353303d54d4514159f7a9b2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 23:40:15 +0200 Subject: [PATCH 045/151] Update using new defaults for memory + reporting width --- tests/core/Makefile | 99 ++++++++++++++++++++++---------------------- tests/core/build.bat | 12 +++--- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 23d0dcf99..6556f2916 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,16 +1,16 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true +COMMON=-no-bounds-check -vet -strict-style COLLECTION=-collection:tests=.. all: all_bsd \ net_test -all_bsd: c_libc_test \ +all_bsd: download_test_assets \ + c_libc_test \ compress_test \ container_test \ crypto_test \ - download_test_assets \ encoding_test \ filepath_test \ fmt_test \ @@ -23,36 +23,26 @@ all_bsd: c_libc_test \ noise_test \ os_exit_test \ reflect_test \ + runtime_test \ slice_test \ strings_test \ thread_test \ - runtime_test \ - time_test \ - fmt_test + time_test download_test_assets: $(PYTHON) download_assets.py -image_test: - $(ODIN) test image $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image +c_libc_test: + $(ODIN) run c/libc $(COMMON) -out:test_core_libc compress_test: - $(ODIN) test compress $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress + $(ODIN) test compress $(COMMON) -out:test_core_compress container_test: - $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container + $(ODIN) test container $(COMMON) -out:test_core_container crypto_test: - $(ODIN) test crypto $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=18 -o:speed -out:test_crypto - -strings_test: - $(ODIN) run strings $(COMMON) -out:test_core_strings - -hash_test: - $(ODIN) run hash $(COMMON) -o:speed -out:test_hash - -noise_test: - $(ODIN) run math/noise $(COMMON) -out:test_noise + $(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto encoding_test: $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa @@ -63,44 +53,53 @@ encoding_test: $(ODIN) run encoding/hex $(COMMON) -out:test_hex $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 -math_test: - $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math - -linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math - filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath -reflect_test: - $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect - -slice_test: - $(ODIN) run slice $(COMMON) -out:test_core_slice - -os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 - -i18n_test: - $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n - -match_test: - $(ODIN) run text/match $(COMMON) -out:test_core_match - -c_libc_test: - $(ODIN) run c/libc $(COMMON) -out:test_core_libc - -net_test: - $(ODIN) run net $(COMMON) -out:test_core_net - fmt_test: $(ODIN) run fmt $(COMMON) -out:test_core_fmt -thread_test: - $(ODIN) run thread $(COMMON) -out:test_core_thread +hash_test: + $(ODIN) run hash $(COMMON) -o:speed -out:test_hash + +i18n_test: + $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n + +image_test: + $(ODIN) test image $(COMMON) -out:test_core_image + +linalg_glsl_math_test: + $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math + +match_test: + $(ODIN) run text/match $(COMMON) -out:test_core_match + +math_test: + $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math + +net_test: + $(ODIN) run net $(COMMON) -out:test_core_net + +noise_test: + $(ODIN) run math/noise $(COMMON) -out:test_noise + +os_exit_test: + $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 + +reflect_test: + $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime +slice_test: + $(ODIN) run slice $(COMMON) -out:test_core_slice + +strings_test: + $(ODIN) run strings $(COMMON) -out:test_core_strings + +thread_test: + $(ODIN) run thread $(COMMON) -out:test_core_thread + time_test: - $(ODIN) run time $(COMMON) -out:test_core_time + $(ODIN) run time $(COMMON) -out:test_core_time \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index b63c0f311..60296090e 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,22 +1,22 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true +set COMMON=-no-bounds-check -vet -strict-style set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% test compress %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests echo --- -%PATH_TO_ODIN% test crypto %COMMON% define:test_progress_width=18 -o:speed -out:test_crypto.exe || exit /b +%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b echo --- echo Running core:encoding tests @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% test image %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b echo --- echo Running core:math tests @@ -107,4 +107,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b +%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file From a27b16721893f78f1788c4f8a7a952d65f7799c3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 10:42:27 +0200 Subject: [PATCH 046/151] Update `tests\core\encoding\cbor` to use new test runner. It was leaky and required a substantial number of `loc := #caller_location` additions to parts of the core library to make it easier to track down how and where it leaked. The tests now run fine multi-threaded. --- core/bufio/reader.odin | 4 +- core/bytes/buffer.odin | 70 ++-- core/encoding/cbor/cbor.odin | 4 +- core/encoding/cbor/coding.odin | 107 +++--- core/encoding/cbor/marshal.odin | 16 +- core/encoding/cbor/unmarshal.odin | 102 +++--- core/strings/builder.odin | 8 +- tests/core/Makefile | 14 +- tests/core/build.bat | 12 +- tests/core/encoding/base64/base64.odin | 65 ++-- tests/core/encoding/cbor/test_core_cbor.odin | 341 +++++++------------ 11 files changed, 323 insertions(+), 420 deletions(-) diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index 8ec736a66..a875c732d 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16 @(private) DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128 -reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) { +reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) { size := size size = max(size, MIN_READ_BUFFER_SIZE) reader_reset(b, rd) b.buf_allocator = allocator - b.buf = make([]byte, size, allocator) + b.buf = make([]byte, size, allocator, loc) } reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) { diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index cb2ef9c62..a7e9b1c64 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -27,19 +27,19 @@ Read_Op :: enum i8 { } -buffer_init :: proc(b: ^Buffer, buf: []byte) { - resize(&b.buf, len(buf)) +buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) { + resize(&b.buf, len(buf), loc=loc) copy(b.buf[:], buf) } -buffer_init_string :: proc(b: ^Buffer, s: string) { - resize(&b.buf, len(s)) +buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) { + resize(&b.buf, len(s), loc=loc) copy(b.buf[:], s) } -buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) { +buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) { if b.buf == nil { - b.buf = make([dynamic]byte, len, cap, allocator) + b.buf = make([dynamic]byte, len, cap, allocator, loc) return } @@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) { } @(private) -_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) { +_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) { if l := len(b.buf); n <= cap(b.buf)-l { - resize(&b.buf, l+n) + resize(&b.buf, l+n, loc=loc) return l, true } return 0, false } @(private) -_buffer_grow :: proc(b: ^Buffer, n: int) -> int { +_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int { m := buffer_length(b) if m == 0 && b.off != 0 { buffer_reset(b) } - if i, ok := _buffer_try_grow(b, n); ok { + if i, ok := _buffer_try_grow(b, n, loc=loc); ok { return i } if b.buf == nil && n <= SMALL_BUFFER_SIZE { // Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator - reserve(&b.buf, SMALL_BUFFER_SIZE) - resize(&b.buf, n) + reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc) + resize(&b.buf, n, loc=loc) return 0 } @@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int { } else if c > max(int) - c - n { panic("bytes.Buffer: too large") } else { - resize(&b.buf, 2*c + n) + resize(&b.buf, 2*c + n, loc=loc) copy(b.buf[:], b.buf[b.off:]) } b.off = 0 - resize(&b.buf, m+n) + resize(&b.buf, m+n, loc=loc) return m } -buffer_grow :: proc(b: ^Buffer, n: int) { +buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { if n < 0 { panic("bytes.buffer_grow: negative count") } - m := _buffer_grow(b, n) - resize(&b.buf, m) + m := _buffer_grow(b, n, loc=loc) + resize(&b.buf, m, loc=loc) } -buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { +buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset return } - _, ok := _buffer_try_grow(b, offset+len(p)) + _, ok := _buffer_try_grow(b, offset+len(p), loc=loc) if !ok { - _ = _buffer_grow(b, offset+len(p)) + _ = _buffer_grow(b, offset+len(p), loc=loc) } if len(b.buf) <= offset { return 0, .Short_Write @@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io. } -buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) { +buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(p)) + m, ok := _buffer_try_grow(b, len(p), loc=loc) if !ok { - m = _buffer_grow(b, len(p)) + m = _buffer_grow(b, len(p), loc=loc) } return copy(b.buf[m:], p), nil } -buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) { - return buffer_write(b, ([^]byte)(ptr)[:size]) +buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) { + return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc) } -buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) { +buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(s)) + m, ok := _buffer_try_grow(b, len(s), loc=loc) if !ok { - m = _buffer_grow(b, len(s)) + m = _buffer_grow(b, len(s), loc=loc) } return copy(b.buf[m:], s), nil } -buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error { +buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, 1) + m, ok := _buffer_try_grow(b, 1, loc=loc) if !ok { - m = _buffer_grow(b, 1) + m = _buffer_grow(b, 1, loc=loc) } b.buf[m] = c return nil } -buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) { +buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) { if r < utf8.RUNE_SELF { - buffer_write_byte(b, byte(r)) + buffer_write_byte(b, byte(r), loc=loc) return 1, nil } b.last_read = .Invalid - m, ok := _buffer_try_grow(b, utf8.UTF_MAX) + m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc) if !ok { - m = _buffer_grow(b, utf8.UTF_MAX) + m = _buffer_grow(b, utf8.UTF_MAX, loc=loc) } res: [4]byte res, n = utf8.encode_rune(r) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index d0e406ab1..7897b2a37 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -320,8 +320,8 @@ to_diagnostic_format :: proc { // Turns the given CBOR value into a human-readable string. // See docs on the proc group `diagnose` for more info. -to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { - b := strings.builder_make(allocator) +to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error { + b := strings.builder_make(allocator, loc) w := strings.to_stream(&b) err := to_diagnostic_format_writer(w, val, padding) if err == .EOF { diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 0d276a7a1..07f0637a6 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -95,24 +95,25 @@ decode :: decode_from // Decodes the given string as CBOR. // See docs on the proc group `decode` for more information. -decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { r: strings.Reader strings.reader_init(&r, s) - return decode_from_reader(strings.reader_to_stream(&r), flags, allocator) + return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc) } // Reads a CBOR value from the given reader. // See docs on the proc group `decode` for more information. -decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { return decode_from_decoder( Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, allocator=allocator, + loc = loc, ) } // Reads a CBOR value from the given decoder. // See docs on the proc group `decode` for more information. -decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { context.allocator = allocator d := d @@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC } - v, err = _decode_from_decoder(d) + v, err = _decode_from_decoder(d, {}, allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) { +_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { hdr := hdr r := d.reader if hdr == Header(0) { hdr = _decode_header(r) or_return } @@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, switch maj { case .Unsigned: return _decode_tiny_u8(add) case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil - case .Bytes: return _decode_bytes_ptr(d, add) - case .Text: return _decode_text_ptr(d, add) - case .Array: return _decode_array_ptr(d, add) - case .Map: return _decode_map_ptr(d, add) - case .Tag: return _decode_tag_ptr(d, add) + case .Bytes: return _decode_bytes_ptr(d, add, .Bytes, allocator, loc) + case .Text: return _decode_text_ptr(d, add, allocator, loc) + case .Array: return _decode_array_ptr(d, add, allocator, loc) + case .Map: return _decode_map_ptr(d, add, allocator, loc) + case .Tag: return _decode_tag_ptr(d, add, allocator, loc) case .Other: return _decode_tiny_simple(add) case: return nil, .Bad_Major } @@ -203,27 +204,27 @@ encode :: encode_into // Encodes the CBOR value into binary CBOR allocated on the given allocator. // See the docs on the proc group `encode_into` for more info. -encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) { - b := strings.builder_make(allocator) or_return +encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) { + b := strings.builder_make(allocator, loc) or_return encode_into_builder(&b, v, flags, temp_allocator) or_return return b.buf[:], nil } // Encodes the CBOR value into binary CBOR written to the given builder. // See the docs on the proc group `encode_into` for more info. -encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator) +encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given writer. // See the docs on the proc group `encode_into` for more info. -encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_encoder(Encoder{flags, w, temp_allocator}, v) +encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given encoder. // See the docs on the proc group `encode_into` for more info. -encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { +encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error { e := e if e.temp_allocator.procedure == nil { @@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er return } -_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { - v = new(Bytes) or_return - defer if err != nil { free(v) } +_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) { + v = new(Bytes, allocator, loc) or_return + defer if err != nil { free(v, allocator, loc) } - v^ = _decode_bytes(d, add, type) or_return + v^ = _decode_bytes(d, add, type, allocator, loc) or_return return } -_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) { +_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) { context.allocator = allocator add := add n, scap := _decode_len_str(d, add) or_return - buf := strings.builder_make(0, scap) or_return + buf := strings.builder_make(0, scap, allocator, loc) or_return defer if err != nil { strings.builder_destroy(&buf) } buf_stream := strings.to_stream(&buf) @@ -426,40 +427,40 @@ _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: En return } -_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) { - v = new(Text) or_return +_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) { + v = new(Text, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_text(d, add) or_return + v^ = _decode_text(d, add, allocator, loc) or_return return } -_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) { - return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil +_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil } _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { return _encode_bytes(e, transmute([]byte)val, .Text) } -_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) { - v = new(Array) or_return +_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) { + v = new(Array, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_array(d, add) or_return + v^ = _decode_array(d, add, allocator, loc) or_return return } -_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) { +_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - array := make([dynamic]Value, 0, scap) or_return + array := make([dynamic]Value, 0, scap, allocator, loc) or_return defer if err != nil { - for entry in array { destroy(entry) } - delete(array) + for entry in array { destroy(entry, allocator) } + delete(array, loc) } for i := 0; n == -1 || i < n; i += 1 { - val, verr := _decode_from_decoder(d) + val, verr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && verr == .Break { break } else if verr != nil { @@ -485,39 +486,39 @@ _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error { return nil } -_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) { - v = new(Map) or_return +_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) { + v = new(Map, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_map(d, add) or_return + v^ = _decode_map(d, add, allocator, loc) or_return return } -_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) { +_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - items := make([dynamic]Map_Entry, 0, scap) or_return + items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return defer if err != nil { for entry in items { destroy(entry.key) destroy(entry.value) } - delete(items) + delete(items, loc) } for i := 0; n == -1 || i < n; i += 1 { - key, kerr := _decode_from_decoder(d) + key, kerr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && kerr == .Break { break } else if kerr != nil { return nil, kerr } - value := _decode_from_decoder(d) or_return + value := _decode_from_decoder(d, {}, allocator, loc) or_return append(&items, Map_Entry{ key = key, value = value, - }) or_return + }, loc) or_return } if .Shrink_Excess in d.flags { shrink(&items) } @@ -578,20 +579,20 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { return nil } -_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) { - tag := _decode_tag(d, add) or_return +_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { + tag := _decode_tag(d, add, allocator, loc) or_return if t, ok := tag.?; ok { defer if err != nil { destroy(t.value) } - tp := new(Tag) or_return + tp := new(Tag, allocator, loc) or_return tp^ = t return tp, nil } // no error, no tag, this was the self described CBOR tag, skip it. - return _decode_from_decoder(d) + return _decode_from_decoder(d, {}, allocator, loc) } -_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { +_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) { num := _decode_uint_as_u64(d.reader, add) or_return // CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR. @@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) t := Tag{ number = num, - value = _decode_from_decoder(d) or_return, + value = _decode_from_decoder(d, {}, allocator, loc) or_return, } if nested, ok := t.value.(^Tag); ok { @@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error { } return _encode_f64_exact(w, v) -} +} \ No newline at end of file diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 37c9dd180..775eafd9c 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -45,8 +45,8 @@ marshal :: marshal_into // Marshals the given value into a CBOR byte stream (allocated using the given allocator). // See docs on the `marshal_into` proc group for more info. -marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) { - b, alloc_err := strings.builder_make(allocator) +marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) { + b, alloc_err := strings.builder_make(allocator, loc=loc) // The builder as a stream also returns .EOF if it ran out of memory so this is consistent. if alloc_err != nil { return nil, .EOF @@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a defer if err != nil { strings.builder_destroy(&b) } - if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil { + if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil { return } @@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a // Marshals the given value into a CBOR byte stream written to the given builder. // See docs on the `marshal_into` proc group for more info. -marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { - return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator) +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc) } // Marshals the given value into a CBOR byte stream written to the given writer. // See docs on the `marshal_into` proc group for more info. -marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { encoder := Encoder{flags, w, temp_allocator} - return marshal_into_encoder(encoder, v) + return marshal_into_encoder(encoder, v, loc=loc) } // Marshals the given value into a CBOR byte stream written to the given encoder. // See docs on the `marshal_into` proc group for more info. -marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { +marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) { e := e if e.temp_allocator.procedure == nil { diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index a1524d9f4..c31ba1d92 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -31,8 +31,8 @@ unmarshal :: proc { unmarshal_from_string, } -unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { - err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator) +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { + err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, } // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { sr: strings.Reader r := strings.to_reader(&sr, s) - err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator) + err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { d := d - err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator) + err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca } -_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error { +_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error { context.allocator = allocator context.temp_allocator = temp_allocator v := v @@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat } data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} - return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return)) + return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc) } -_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { v := v ti := reflect.type_info_base(type_info_of(v.id)) r := d.reader @@ -104,7 +104,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err // Allow generic unmarshal by doing it into a `Value`. switch &dst in v { case Value: - dst = err_conv(_decode_from_decoder(d, hdr)) or_return + dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return return } @@ -308,7 +308,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err if impl, ok := _tag_implementations_nr[nr]; ok { return impl->unmarshal(d, nr, v) } else if nr == TAG_OBJECT_TYPE { - return _unmarshal_union(d, v, ti, hdr) + return _unmarshal_union(d, v, ti, hdr, loc=loc) } else { // Discard the tag info and unmarshal as its value. return _unmarshal_value(d, v, _decode_header(r) or_return) @@ -316,19 +316,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err return _unsupported(v, hdr, add) - case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add) - case .Text: return _unmarshal_string(d, v, ti, hdr, add) - case .Array: return _unmarshal_array(d, v, ti, hdr, add) - case .Map: return _unmarshal_map(d, v, ti, hdr, add) + case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Text: return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Map: return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc) case: return .Bad_Major } } -_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -347,7 +347,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Slice)(v.data) raw^ = transmute(mem.Raw_Slice)bytes return @@ -357,12 +357,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(bytes) raw.len = len(bytes) raw.cap = len(bytes) - raw.allocator = context.allocator + raw.allocator = allocator return case reflect.Type_Info_Array: @@ -385,10 +385,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } -_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - text := err_conv(_decode_text(d, add)) or_return + text := err_conv(_decode_text(d, add, allocator, loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -403,8 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade // Enum by its variant name. case reflect.Type_Info_Enum: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) for name, i in t.names { if name == text { @@ -414,8 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade } case reflect.Type_Info_Rune: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) r := (^rune)(v.data) dr, n := utf8.decode_rune(text) @@ -430,13 +430,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade return _unsupported(v, hdr) } -_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { assign_array :: proc( d: Decoder, da: ^mem.Raw_Dynamic_Array, elemt: ^reflect.Type_Info, length: int, growable := true, + allocator := context.allocator, + loc := #caller_location, ) -> (out_of_space: bool, err: Unmarshal_Error) { for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 { elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) @@ -450,13 +452,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if !growable { return true, .Out_Of_Memory } cap := 2 * da.cap - ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap) + ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc) // NOTE: Might be lying here, but it is at least an allocator error. if !ok { return false, .Out_Of_Memory } } - err = _unmarshal_value(d, elem, hdr) + err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc) if length == -1 && err == .Break { break } if err != nil { return } @@ -469,10 +471,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header // Allow generically storing the values array. switch &dst in v { case ^Array: - dst = err_conv(_decode_array_ptr(d, add)) or_return + dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Array: - dst = err_conv(_decode_array(d, add)) or_return + dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return return } @@ -480,8 +482,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Slice: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } @@ -489,7 +491,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len) + _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc) } raw := (^mem.Raw_Slice)(v.data) @@ -500,8 +502,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Dynamic_Array: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(data) @@ -513,7 +515,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len) + _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc) } return @@ -525,7 +527,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } @@ -539,7 +541,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } @@ -553,7 +555,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator } info: ^runtime.Type_Info switch ti.id { @@ -575,7 +577,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator } info: ^runtime.Type_Info switch ti.id { @@ -593,17 +595,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header } } -_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader - decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) { + decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) { entry_hdr := _decode_header(d.reader) or_return entry_maj, entry_add := _header_split(entry_hdr) #partial switch entry_maj { case .Text: - k = err_conv(_decode_text(d, entry_add, allocator)) or_return + k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return return case .Bytes: - bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return + bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return k = string(bytes) return case: @@ -615,10 +617,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Allow generically storing the map array. switch &dst in v { case ^Map: - dst = err_conv(_decode_map_ptr(d, add)) or_return + dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Map: - dst = err_conv(_decode_map(d, add)) or_return + dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return return } @@ -754,7 +756,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which // contains an array of exactly two elements, the first is a textual representation of the following // CBOR value's type. -_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader #partial switch t in ti.variant { case reflect.Type_Info_Union: @@ -792,7 +794,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Named: if vti.name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } case: @@ -804,7 +806,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if variant_name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } } } diff --git a/core/strings/builder.odin b/core/strings/builder.odin index 72eb815f9..11885b689 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -350,9 +350,9 @@ Output: ab */ -write_byte :: proc(b: ^Builder, x: byte) -> (n: int) { +write_byte :: proc(b: ^Builder, x: byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, x) + append(&b.buf, x, loc) n1 := len(b.buf) return n1-n0 } @@ -380,9 +380,9 @@ NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` Returns: - n: The number of bytes appended */ -write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) { +write_bytes :: proc(b: ^Builder, x: []byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, ..x) + append(&b.buf, ..x, loc=loc) n1 := len(b.buf) return n1-n0 } diff --git a/tests/core/Makefile b/tests/core/Makefile index 6556f2916..0a1055120 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -45,13 +45,13 @@ crypto_test: $(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto encoding_test: - $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa - $(ODIN) run encoding/json $(COMMON) -out:test_json - $(ODIN) run encoding/varint $(COMMON) -out:test_varint - $(ODIN) run encoding/xml $(COMMON) -out:test_xml - $(ODIN) run encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) run encoding/hex $(COMMON) -out:test_hex - $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 + $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 + $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor + $(ODIN) run encoding/hex $(COMMON) -out:test_hex + $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa + $(ODIN) run encoding/json $(COMMON) -out:test_json + $(ODIN) run encoding/varint $(COMMON) -out:test_varint + $(ODIN) run encoding/xml $(COMMON) -out:test_xml filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath diff --git a/tests/core/build.bat b/tests/core/build.bat index 60296090e..a26ccf176 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -21,13 +21,13 @@ echo --- echo --- echo Running core:encoding tests echo --- +%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b +%PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:ODIN_TEST_THREADS=1 -define:ODIN_TEST_FANCY=false || exit /b -%PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b -%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b +%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b +%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b +%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- echo Running core:fmt tests diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin index e48eea020..6679c8ce2 100644 --- a/tests/core/encoding/base64/base64.odin +++ b/tests/core/encoding/base64/base64.odin @@ -1,61 +1,38 @@ package test_encoding_base64 import "base:intrinsics" - import "core:encoding/base64" -import "core:fmt" -import "core:os" -import "core:reflect" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value - -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } +Test :: struct { + vector: string, + base64: string, } -main :: proc() { - t := testing.T{} - - test_encoding(&t) - test_decoding(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } +tests :: []Test{ + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, } @(test) test_encoding :: proc(t: ^testing.T) { - expect_value(t, base64.encode(transmute([]byte)string("")), "") - expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==") - expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=") - expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v") - expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==") - expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=") - expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy") + for test in tests { + v := base64.encode(transmute([]byte)test.vector) + defer delete(v) + testing.expect_value(t, v, test.base64) + } } @(test) test_decoding :: proc(t: ^testing.T) { - expect_value(t, string(base64.decode("")), "") - expect_value(t, string(base64.decode("Zg==")), "f") - expect_value(t, string(base64.decode("Zm8=")), "fo") - expect_value(t, string(base64.decode("Zm9v")), "foo") - expect_value(t, string(base64.decode("Zm9vYg==")), "foob") - expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba") - expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar") + for test in tests { + v := string(base64.decode(test.base64)) + defer delete(v) + testing.expect_value(t, v, test.vector) + } } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 72244e1d3..d069ef05b 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -1,105 +1,15 @@ package test_encoding_cbor import "base:intrinsics" - import "core:bytes" import "core:encoding/cbor" import "core:fmt" import "core:io" import "core:math/big" -import "core:mem" -import "core:os" import "core:reflect" import "core:testing" import "core:time" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - errorf :: testing.errorf - log :: testing.log - -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } - - errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v] ERROR: ", loc) - fmt.printf(fmts, ..args) - fmt.println() - } - - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_marshalling(&t) - - test_marshalling_maybe(&t) - test_marshalling_nil_maybe(&t) - - test_marshalling_union(&t) - - test_lying_length_array(&t) - - test_decode_unsigned(&t) - test_encode_unsigned(&t) - - test_decode_negative(&t) - test_encode_negative(&t) - - test_decode_simples(&t) - test_encode_simples(&t) - - test_decode_floats(&t) - test_encode_floats(&t) - - test_decode_bytes(&t) - test_encode_bytes(&t) - - test_decode_strings(&t) - test_encode_strings(&t) - - test_decode_lists(&t) - test_encode_lists(&t) - - test_decode_maps(&t) - test_encode_maps(&t) - - test_decode_tags(&t) - test_encode_tags(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - Foo :: struct { str: string, cstr: cstring, @@ -143,14 +53,6 @@ FooBars :: bit_set[FooBar; u16] @(test) test_marshalling :: proc(t: ^testing.T) { - tracker: mem.Tracking_Allocator - mem.tracking_allocator_init(&tracker, context.allocator) - context.allocator = mem.tracking_allocator(&tracker) - context.temp_allocator = context.allocator - defer mem.tracking_allocator_destroy(&tracker) - - ev :: expect_value - { nice := "16 is a nice number" now := time.Time{_nsec = 1701117968 * 1e9} @@ -205,18 +107,18 @@ test_marshalling :: proc(t: ^testing.T) { } data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC) - ev(t, err, nil) + testing.expect_value(t, err, nil) defer delete(data) decoded, derr := cbor.decode(string(data)) - ev(t, derr, nil) + testing.expect_value(t, derr, nil) defer cbor.destroy(decoded) diagnosis, eerr := cbor.to_diagnostic_format(decoded) - ev(t, eerr, nil) + testing.expect_value(t, eerr, nil) defer delete(diagnosis) - ev(t, diagnosis, `{ + testing.expect_value(t, diagnosis, `{ "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), "biggest": 2(h'f951a9fd3c158afdff08ab8e0'), "biggie": 18446744073709551615, @@ -285,7 +187,7 @@ test_marshalling :: proc(t: ^testing.T) { backf: Foo uerr := cbor.unmarshal(string(data), &backf) - ev(t, uerr, nil) + testing.expect_value(t, uerr, nil) defer { delete(backf.str) delete(backf.cstr) @@ -304,104 +206,102 @@ test_marshalling :: proc(t: ^testing.T) { big.destroy(&backf.smallest) } - ev(t, backf.str, f.str) - ev(t, backf.cstr, f.cstr) + testing.expect_value(t, backf.str, f.str) + testing.expect_value(t, backf.cstr, f.cstr) #partial switch v in backf.value { case ^cbor.Map: for entry, i in v { fm := f.value.(^cbor.Map) - ev(t, entry.key, fm[i].key) + testing.expect_value(t, entry.key, fm[i].key) if str, is_str := entry.value.(^cbor.Text); is_str { - ev(t, str^, fm[i].value.(^cbor.Text)^) + testing.expect_value(t, str^, fm[i].value.(^cbor.Text)^) } else { - ev(t, entry.value, fm[i].value) + testing.expect_value(t, entry.value, fm[i].value) } } - case: errorf(t, "wrong type %v", v) + case: testing.expectf(t, false, "wrong type %v", v) } - ev(t, backf.neg, f.neg) - ev(t, backf.iamint, f.iamint) - ev(t, backf.base64, f.base64) - ev(t, backf.renamed, f.renamed) - ev(t, backf.now, f.now) - ev(t, backf.nowie, f.nowie) - for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) } - for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) } - ev(t, backf.child.my_integers, f.child.my_integers) - ev(t, len(backf.my_bytes), 0) - ev(t, len(backf.my_bytes), len(f.my_bytes)) - ev(t, backf.ennie, f.ennie) - ev(t, backf.ennieb, f.ennieb) - ev(t, backf.quat, f.quat) - ev(t, backf.comp, f.comp) - ev(t, backf.important, f.important) - ev(t, backf.no, nil) - ev(t, backf.nos, nil) - ev(t, backf.yes, f.yes) - ev(t, backf.biggie, f.biggie) - ev(t, backf.smallie, f.smallie) - ev(t, backf.onetwenty, f.onetwenty) - ev(t, backf.small_onetwenty, f.small_onetwenty) - ev(t, backf.ignore_this, nil) + testing.expect_value(t, backf.neg, f.neg) + testing.expect_value(t, backf.iamint, f.iamint) + testing.expect_value(t, backf.base64, f.base64) + testing.expect_value(t, backf.renamed, f.renamed) + testing.expect_value(t, backf.now, f.now) + testing.expect_value(t, backf.nowie, f.nowie) + for e, i in f.child.dyn { testing.expect_value(t, backf.child.dyn[i], e) } + for key, value in f.child.mappy { testing.expect_value(t, backf.child.mappy[key], value) } + testing.expect_value(t, backf.child.my_integers, f.child.my_integers) + testing.expect_value(t, len(backf.my_bytes), 0) + testing.expect_value(t, len(backf.my_bytes), len(f.my_bytes)) + testing.expect_value(t, backf.ennie, f.ennie) + testing.expect_value(t, backf.ennieb, f.ennieb) + testing.expect_value(t, backf.quat, f.quat) + testing.expect_value(t, backf.comp, f.comp) + testing.expect_value(t, backf.important, f.important) + testing.expect_value(t, backf.no, nil) + testing.expect_value(t, backf.nos, nil) + testing.expect_value(t, backf.yes, f.yes) + testing.expect_value(t, backf.biggie, f.biggie) + testing.expect_value(t, backf.smallie, f.smallie) + testing.expect_value(t, backf.onetwenty, f.onetwenty) + testing.expect_value(t, backf.small_onetwenty, f.small_onetwenty) + testing.expect_value(t, backf.ignore_this, nil) s_equals, s_err := big.equals(&backf.smallest, &f.smallest) - ev(t, s_err, nil) + testing.expect_value(t, s_err, nil) if !s_equals { - errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) + testing.expectf(t, false, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) } b_equals, b_err := big.equals(&backf.biggest, &f.biggest) - ev(t, b_err, nil) + testing.expect_value(t, b_err, nil) if !b_equals { - errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) + testing.expectf(t, false, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) } } - - for _, leak in tracker.allocation_map { - errorf(t, "%v leaked %m\n", leak.location, leak.size) - } - - for bad_free in tracker.bad_free_array { - errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } } @(test) test_marshalling_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) = 1 data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "1") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "1") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, 1) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, 1) } @(test) test_marshalling_nil_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "nil") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "nil") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, nil) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, nil) } @(test) @@ -427,17 +327,24 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union = My_Distinct("Hello, World!") data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Distinct", "Hello, World!"])`) dest: My_Union uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Distinct("Hello, World!")) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Distinct("Hello, World!")) + if str, ok := dest.(My_Distinct); ok { + delete(string(str)) + } } My_Union_No_Nil :: union #no_nil { @@ -450,17 +357,21 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union_No_Nil = My_Struct{.Two} data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Struct", {"my_enum": 1}])`) dest: My_Union_No_Nil uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Struct{.Two}) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Struct{.Two}) } } @@ -469,7 +380,7 @@ test_lying_length_array :: proc(t: ^testing.T) { // Input says this is an array of length max(u64), this should not allocate that amount. input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42} _, err := cbor.decode(string(input)) - expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. + testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. } @(test) @@ -691,65 +602,73 @@ test_encode_lists :: proc(t: ^testing.T) { expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{}) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(1)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)}) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(4)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, u8(5)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) } { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor._encode_u8(stream, 2, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) a := "a" err = cbor.encode(encoder, &a) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_begin(stream, .Map) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) b := "b" c := "c" err = cbor.encode_stream_map_entry(encoder, &b, &c) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) } } @@ -807,30 +726,30 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), type, loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) + testing.expect_value(t, err, nil, loc) str := cbor.to_diagnostic_format(res, padding=-1) defer delete(str) - expect_value(t, str, decoded, loc) + testing.expect_value(t, str, decoded, loc) } expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) if tag, is_tag := res.(^cbor.Tag); is_tag { - expect_value(t, tag.number, nr, loc) + testing.expect_value(t, tag.number, nr, loc) str := cbor.to_diagnostic_format(tag, padding=-1) defer delete(str) - expect_value(t, str, value_decoded, loc) + testing.expect_value(t, str, value_decoded, loc) } else { - errorf(t, "Value %#v is not a tag", res, loc) + testing.expectf(t, false, "Value %#v is not a tag", res, loc) } } @@ -838,35 +757,39 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) + testing.expect_value(t, err, nil, loc) #partial switch r in res { case f16: - when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f32: - when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f64: - when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case: unreachable() } } -buf: bytes.Buffer -stream := bytes.buffer_to_stream(&buf) -encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - err := cbor.encode(encoder, val) - expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + err := cbor.encode(encoder, val, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} for value, i in values { err: cbor.Encode_Error @@ -891,15 +814,15 @@ expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor. if err2 != nil { break } } case: - errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) + testing.expectf(t, false, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) } - expect_value(t, err, nil, loc) - expect_value(t, err2, nil, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err2, nil, loc) } err := cbor.encode_stream_end(stream) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } From 1f6a6f2cd367aec2e506ae8d5fafcb34d4b2a0e3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 18:04:59 -0400 Subject: [PATCH 047/151] Support deterministic random seeding of tests Add a new option `ODIN_TEST_RANDOM_SEED` which is picked from the cycle counter at startup, if it's not specified by the user. This number is sent to every test in the `T` struct and reset every test (just in case). --- core/testing/runner.odin | 14 ++++++++++++++ core/testing/testing.odin | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 759b23b0f..faba54286 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -9,6 +9,7 @@ import "core:encoding/base64" import "core:fmt" import "core:io" import pkg_log "core:log" +import "core:math/rand" import "core:mem" import "core:os" import "core:slice" @@ -38,6 +39,9 @@ FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) +// This is the random seed that will be sent to each test. +// If it is unspecified, it will be set to the system cycle counter at startup. +SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) end_t :: proc(t: ^T) { @@ -333,6 +337,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer bytes.buffer_destroy(&clipboard_buffer) } + when SHARED_RANDOM_SEED == 0 { + shared_random_seed := cast(u64)intrinsics.read_cycle_counter() + } else { + shared_random_seed := SHARED_RANDOM_SEED + } + // -- Setup initial tasks. // NOTE(Feoramund): This is the allocator that will be used by threads to @@ -356,6 +366,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer run_index += 1 data.it = it + data.t.seed = shared_random_seed #no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel) data.t._log_allocator = shared_log_allocator data.allocator_index = task_index @@ -383,6 +394,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } + pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) + when TRACKING_MEMORY { when ALWAYS_REPORT_MEMORY { pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") @@ -428,6 +441,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer run_index += 1 data.it = it + data.t.seed = shared_random_seed data.t.error_count = 0 thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index) diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 30109304d..92b4d391d 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -29,6 +29,16 @@ Internal_Cleanup :: struct { T :: struct { error_count: int, + // If your test needs to perform random operations, it's advised to use + // this value to seed a local random number generator rather than relying + // on the non-thread-safe global one. + // + // This way, your results will be deterministic. + // + // This value is chosen at startup of the test runner, logged, and may be + // specified by the user. It is the same for all tests of a single run. + seed: u64, + channel: Update_Channel_Sender, cleanups: [dynamic]Internal_Cleanup, From b74b956fdaccbd930851e705ecc3e7d0f5d8a957 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 18:09:41 -0400 Subject: [PATCH 048/151] Remove unneeded import --- core/testing/runner.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index faba54286..47ae5d528 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -9,7 +9,6 @@ import "core:encoding/base64" import "core:fmt" import "core:io" import pkg_log "core:log" -import "core:math/rand" import "core:mem" import "core:os" import "core:slice" From 40b20fb47353e61681ef815c43423d76b99e61d9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 11:32:41 +0200 Subject: [PATCH 049/151] Port `tests\core\c\libc` --- tests/core/Makefile | 2 +- tests/core/build.bat | 5 +++ tests/core/c/libc/test_core_libc.odin | 36 ------------------- .../c/libc/test_core_libc_complex_pow.odin | 15 ++++---- 4 files changed, 13 insertions(+), 45 deletions(-) delete mode 100644 tests/core/c/libc/test_core_libc.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index 0a1055120..4073da3f1 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -33,7 +33,7 @@ download_test_assets: $(PYTHON) download_assets.py c_libc_test: - $(ODIN) run c/libc $(COMMON) -out:test_core_libc + $(ODIN) test c/libc $(COMMON) -out:test_core_libc compress_test: $(ODIN) test compress $(COMMON) -out:test_core_compress diff --git a/tests/core/build.bat b/tests/core/build.bat index a26ccf176..69a7d432a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -3,6 +3,11 @@ set COMMON=-no-bounds-check -vet -strict-style set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py +echo --- +echo Running core:c/libc tests +echo --- +%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b + echo --- echo Running core:compress tests echo --- diff --git a/tests/core/c/libc/test_core_libc.odin b/tests/core/c/libc/test_core_libc.odin deleted file mode 100644 index 9b5014dee..000000000 --- a/tests/core/c/libc/test_core_libc.odin +++ /dev/null @@ -1,36 +0,0 @@ -package test_core_libc - -import "core:fmt" -import "core:os" -import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_libc_complex(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} diff --git a/tests/core/c/libc/test_core_libc_complex_pow.odin b/tests/core/c/libc/test_core_libc_complex_pow.odin index 90928794c..cd50c8f6a 100644 --- a/tests/core/c/libc/test_core_libc_complex_pow.odin +++ b/tests/core/c/libc/test_core_libc_complex_pow.odin @@ -1,8 +1,8 @@ package test_core_libc import "core:testing" -import "core:fmt" import "core:c/libc" +import "core:log" reldiff :: proc(lhs, rhs: $T) -> f64 { if lhs == rhs { @@ -14,7 +14,7 @@ reldiff :: proc(lhs, rhs: $T) -> f64 { return out } -isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { +isclose :: proc(t: ^testing.T, lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { adiff := f64(abs(lhs - rhs)) if adiff < atol { return true @@ -23,7 +23,7 @@ isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { if rdiff < rtol { return true } - fmt.printf("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) + log.infof("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) return false } @@ -44,7 +44,6 @@ test_libc_complex :: proc(t: ^testing.T) { test_libc_pow_binding(t, libc.complex_float, f32, libc_powf, 1e-12, 1e-5) } -@test test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, pow: proc(LIBC_COMPLEX, LIBC_COMPLEX) -> LIBC_COMPLEX, rtol: f64, atol: f64) { // Tests that c/libc/pow(f) functions have two arguments and that the function works as expected for simple inputs @@ -56,8 +55,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po for n in -4..=4 { complex_power := LIBC_COMPLEX(complex(F(n), F(0.))) result := pow(complex_base, complex_power) - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) expected_real *= 2 } } @@ -83,8 +82,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po expected_real = 0. expected_imag = -value } - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) value *= 2 } } From 3404dea8ac741f98e3de4a341f64ef37d06550d9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 11:42:34 +0200 Subject: [PATCH 050/151] Port `tests\encoding\hex` --- core/encoding/hex/hex.odin | 11 ++- tests/core/Makefile | 2 +- tests/core/encoding/hex/test_core_hex.odin | 94 ++++++++++------------ 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index dbffe216b..c2cd89c5b 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -2,8 +2,8 @@ package encoding_hex import "core:strings" -encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check { - dst := make([]byte, len(src) * 2, allocator) +encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check { + dst := make([]byte, len(src) * 2, allocator, loc) for i, j := 0, 0; i < len(src); i += 1 { v := src[i] dst[j] = HEXTABLE[v>>4] @@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds } -decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check { +decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check { if len(src) % 2 == 1 { return } - dst = make([]byte, len(src) / 2, allocator) + dst = make([]byte, len(src) / 2, allocator, loc) for i, j := 0, 1; j < len(src); j += 2 { p := src[j-1] q := src[j] @@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) { case 'A' ..= 'F': return char - 'A' + 10, true case: return 0, false } -} - +} \ No newline at end of file diff --git a/tests/core/Makefile b/tests/core/Makefile index 4073da3f1..93d141e60 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -47,7 +47,7 @@ crypto_test: encoding_test: $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) run encoding/hex $(COMMON) -out:test_hex + $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index d928cd28e..6a00c9705 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -2,42 +2,6 @@ package test_core_hex import "core:encoding/hex" import "core:testing" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - hex_encode(&t) - hex_decode(&t) - hex_decode_sequence(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} CASES :: [][2]string{ {"11", "3131"}, @@ -49,10 +13,14 @@ CASES :: [][2]string{ hex_encode :: proc(t: ^testing.T) { for test in CASES { encoded := string(hex.encode(transmute([]byte)test[0])) - expect( + defer delete(encoded) + testing.expectf( t, encoded == test[1], - fmt.tprintf("encode: %q -> %q (should be: %q)", test[0], encoded, test[1]), + "encode: %q -> %q (should be: %q)", + test[0], + encoded, + test[1], ) } } @@ -61,11 +29,20 @@ hex_encode :: proc(t: ^testing.T) { hex_decode :: proc(t: ^testing.T) { for test in CASES { decoded, ok := hex.decode(transmute([]byte)test[1]) - expect(t, ok, fmt.tprintf("decode: %q not ok", test[1])) - expect( + defer delete(decoded) + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( t, string(decoded) == test[0], - fmt.tprintf("decode: %q -> %q (should be: %q)", test[1], string(decoded), test[0]), + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], ) } } @@ -73,20 +50,37 @@ hex_decode :: proc(t: ^testing.T) { @(test) hex_decode_sequence :: proc(t: ^testing.T) { b, ok := hex.decode_sequence("0x23") - expect(t, ok, "decode_sequence: 0x23 not ok") - expect(t, b == '#', fmt.tprintf("decode_sequence: 0x23 -> %c (should be: %c)", b, '#')) + testing.expect(t, ok, "decode_sequence: 0x23 not ok") + testing.expectf( + t, + b == '#', + "decode_sequence: 0x23 -> %c (should be: %c)", + b, + '#', + ) b, ok = hex.decode_sequence("0X3F") - expect(t, ok, "decode_sequence: 0X3F not ok") - expect(t, b == '?', fmt.tprintf("decode_sequence: 0X3F -> %c (should be: %c)", b, '?')) + testing.expect(t, ok, "decode_sequence: 0X3F not ok") + testing.expectf( + t, + b == '?', + "decode_sequence: 0X3F -> %c (should be: %c)", + b, + '?', + ) b, ok = hex.decode_sequence("2a") - expect(t, ok, "decode_sequence: 2a not ok") - expect(t, b == '*', fmt.tprintf("decode_sequence: 2a -> %c (should be: %c)", b, '*')) + testing.expect(t, ok, "decode_sequence: 2a not ok") + testing.expectf(t, + b == '*', + "decode_sequence: 2a -> %c (should be: %c)", + b, + '*', + ) _, ok = hex.decode_sequence("1") - expect(t, !ok, "decode_sequence: 1 should be too short") + testing.expect(t, !ok, "decode_sequence: 1 should be too short") _, ok = hex.decode_sequence("123") - expect(t, !ok, "decode_sequence: 123 should be too long") -} + testing.expect(t, !ok, "decode_sequence: 123 should be too long") +} \ No newline at end of file From 1b32e27aa47c59a71b1cf4fefd40ce6e5ffc5bfb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 13:10:38 +0200 Subject: [PATCH 051/151] Port `tests\core\encoding\hxa` And fix a few leaks in `core:encoding/hxa` while at it. --- core/encoding/hxa/hxa.odin | 31 ++-- core/encoding/hxa/read.odin | 42 ++--- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/hxa/test_core_hxa.odin | 202 +++++++++------------ 5 files changed, 125 insertions(+), 154 deletions(-) diff --git a/core/encoding/hxa/hxa.odin b/core/encoding/hxa/hxa.odin index 9b24ede9c..9d0c58196 100644 --- a/core/encoding/hxa/hxa.odin +++ b/core/encoding/hxa/hxa.odin @@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform" /* destroy procedures */ -meta_destroy :: proc(meta: Meta, allocator := context.allocator) { +meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) { if nested, ok := meta.value.([]Meta); ok { for m in nested { - meta_destroy(m) + meta_destroy(m, loc=loc) } - delete(nested, allocator) + delete(nested, allocator, loc=loc) } } -nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) { +nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) { for node in nodes { for meta in node.meta_data { - meta_destroy(meta) + meta_destroy(meta, loc=loc) } - delete(node.meta_data, allocator) + delete(node.meta_data, allocator, loc=loc) switch n in node.content { case Node_Geometry: - delete(n.corner_stack, allocator) - delete(n.edge_stack, allocator) - delete(n.face_stack, allocator) + delete(n.corner_stack, allocator, loc=loc) + delete(n.vertex_stack, allocator, loc=loc) + delete(n.edge_stack, allocator, loc=loc) + delete(n.face_stack, allocator, loc=loc) case Node_Image: - delete(n.image_stack, allocator) + delete(n.image_stack, allocator, loc=loc) } } - delete(nodes, allocator) + delete(nodes, allocator, loc=loc) } -file_destroy :: proc(file: File) { - nodes_destroy(file.nodes, file.allocator) - delete(file.backing, file.allocator) -} +file_destroy :: proc(file: File, loc := #caller_location) { + nodes_destroy(file.nodes, file.allocator, loc=loc) + delete(file.backing, file.allocator, loc=loc) +} \ No newline at end of file diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index f37dc3193..5c8503229 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -11,24 +11,21 @@ Read_Error :: enum { Unable_To_Read_File, } -read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) + data, ok := os.read_entire_file(filename, allocator, loc) if !ok { err = .Unable_To_Read_File + delete(data, allocator, loc) return } - defer if !ok { - delete(data) - } else { - file.backing = data - } - file, err = read(data, filename, print_error, allocator) + file, err = read(data, filename, print_error, allocator, loc) + file.backing = data return } -read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { Reader :: struct { filename: string, data: []byte, @@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return string(data[:len]), nil } - read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) { - meta_data = make([]Meta, int(capacity)) + read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) { + meta_data = make([]Meta, int(capacity), allocator=allocator) count := 0 defer meta_data = meta_data[:count] for &m in meta_data { @@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return } - read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) { + read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) { stack_count := read_value(r, u32le) or_return layer_count := 0 - layers = make(Layer_Stack, stack_count) + layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc) defer layers = layers[:layer_count] for &layer in layers { layer.name = read_name(r) or_return @@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato node_count := 0 file.header = header^ - file.nodes = make([]Node, header.internal_node_count) + file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc) + file.allocator = allocator defer if err != nil { nodes_destroy(file.nodes) file.nodes = nil @@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato case .Geometry: g: Node_Geometry - g.vertex_count = read_value(r, u32le) or_return - g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return - g.edge_corner_count = read_value(r, u32le) or_return - g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.vertex_count = read_value(r, u32le) or_return + g.vertex_stack = read_layer_stack(r, g.vertex_count, loc=loc) or_return + g.edge_corner_count = read_value(r, u32le) or_return + g.corner_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return if header.version > 2 { - g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return } - g.face_count = read_value(r, u32le) or_return - g.face_stack = read_layer_stack(r, g.face_count) or_return + g.face_count = read_value(r, u32le) or_return + g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return node.content = g @@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato } return -} +} \ No newline at end of file diff --git a/tests/core/Makefile b/tests/core/Makefile index 93d141e60..46b9e352e 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -48,7 +48,7 @@ encoding_test: $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor $(ODIN) test encoding/hex $(COMMON) -out:test_hex - $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa + $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml diff --git a/tests/core/build.bat b/tests/core/build.bat index 69a7d432a..094a12f14 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -29,7 +29,7 @@ echo --- %PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b %PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b -rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b +%PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index 40c3c2e23..31d40c8b3 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -6,127 +6,99 @@ package test_core_hxa import "core:encoding/hxa" import "core:fmt" import "core:testing" -import tc "tests:common" -TEAPOT_PATH :: "core/assets/HXA/teapot.hxa" +TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa" -main :: proc() { - t := testing.T{} - - test_read(&t) - test_write(&t) - - tc.report(&t) -} +import "core:os" @test test_read :: proc(t: ^testing.T) { - filename := tc.get_data_path(t, TEAPOT_PATH) - defer delete(filename) + data, _ := os.read_entire_file(TEAPOT_PATH) + // file, err := hxa.read_from_file(TEAPOT_PATH) + file, err := hxa.read(data) + file.backing = data + file.allocator = context.allocator + hxa.file_destroy(file) + // fmt.printfln("%#v", file) - file, err := hxa.read_from_file(filename) e :: hxa.Read_Error.None - tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e)) - defer hxa.file_destroy(file) + testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e) /* Header */ - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 1, fmt.tprintf("%v: file.version %v != %v", - #procedure, file.version, 1)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, "file.magic_number %v != %v", file.magic_number, 0x417848) + testing.expectf(t, file.version == 1, "file.version %v != %v", file.version, 1) + testing.expectf(t, file.internal_node_count == 1, "file.internal_node_count %v != %v", file.internal_node_count, 1) /* Nodes (only one) */ - tc.expect(t, len(file.nodes) == 1, fmt.tprintf("%v: len(file.nodes) %v != %v", #procedure, len(file.nodes), 1)) + testing.expectf(t, len(file.nodes) == 1, "len(file.nodes) %v != %v", len(file.nodes), 1) m := &file.nodes[0].meta_data - tc.expect(t, len(m^) == 38, fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), 38)) + testing.expectf(t, len(m^) == 38, "len(m^) %v != %v", len(m^), 38) { e :: "Texture resolution" - tc.expect(t, m[0].name == e, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, e)) + testing.expectf(t, m[0].name == e, "m[0].name %v != %v", m[0].name, e) m_v, m_v_ok := m[0].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == 1024, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), 1024)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == 1024, "m_v[0] %v != %v", len(m_v), 1024) } { e :: "Validate" - tc.expect(t, m[37].name == e, fmt.tprintf("%v: m[37].name %v != %v", #procedure, m[37].name, e)) + testing.expectf(t, m[37].name == e, "m[37].name %v != %v", m[37].name, e) m_v, m_v_ok := m[37].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == -2054847231, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), -2054847231)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == -2054847231, "m_v[0] %v != %v", len(m_v), -2054847231) } /* Node content */ v, v_ok := file.nodes[0].content.(hxa.Node_Geometry) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) + testing.expectf(t, v_ok, "v_ok %v != %v", v_ok, true) - tc.expect(t, v.vertex_count == 530, fmt.tprintf("%v: v.vertex_count %v != %v", #procedure, v.vertex_count, 530)) - tc.expect(t, v.edge_corner_count == 2026, fmt.tprintf("%v: v.edge_corner_count %v != %v", - #procedure, v.edge_corner_count, 2026)) - tc.expect(t, v.face_count == 517, fmt.tprintf("%v: v.face_count %v != %v", #procedure, v.face_count, 517)) + testing.expectf(t, v.vertex_count == 530, "v.vertex_count %v != %v", v.vertex_count, 530) + testing.expectf(t, v.edge_corner_count == 2026, "v.edge_corner_count %v != %v", v.edge_corner_count, 2026) + testing.expectf(t, v.face_count == 517, "v.face_count %v != %v", v.face_count, 517) /* Vertex stack */ - tc.expect(t, len(v.vertex_stack) == 1, fmt.tprintf("%v: len(v.vertex_stack) %v != %v", - #procedure, len(v.vertex_stack), 1)) + testing.expectf(t, len(v.vertex_stack) == 1, "len(v.vertex_stack) %v != %v", len(v.vertex_stack), 1) { e := "vertex" - tc.expect(t, v.vertex_stack[0].name == e, fmt.tprintf("%v: v.vertex_stack[0].name %v != %v", - #procedure, v.vertex_stack[0].name, e)) + testing.expectf(t, v.vertex_stack[0].name == e, "v.vertex_stack[0].name %v != %v", v.vertex_stack[0].name, e) } - tc.expect(t, v.vertex_stack[0].components == 3, fmt.tprintf("%v: v.vertex_stack[0].components %v != %v", - #procedure, v.vertex_stack[0].components, 3)) + testing.expectf(t, v.vertex_stack[0].components == 3, "v.vertex_stack[0].components %v != %v", v.vertex_stack[0].components, 3) /* Vertex stack data */ vs_d, vs_d_ok := v.vertex_stack[0].data.([]f64le) - tc.expect(t, vs_d_ok, fmt.tprintf("%v: vs_d_ok %v != %v", #procedure, vs_d_ok, true)) - tc.expect(t, len(vs_d) == 1590, fmt.tprintf("%v: len(vs_d) %v != %v", #procedure, len(vs_d), 1590)) - - tc.expect(t, vs_d[0] == 4.06266, fmt.tprintf("%v: vs_d[0] %v (%h) != %v (%h)", - #procedure, vs_d[0], vs_d[0], 4.06266, 4.06266)) - tc.expect(t, vs_d[1] == 2.83457, fmt.tprintf("%v: vs_d[1] %v (%h) != %v (%h)", - #procedure, vs_d[1], vs_d[1], 2.83457, 2.83457)) - tc.expect(t, vs_d[2] == 0hbfbc5da6a4441787, fmt.tprintf("%v: vs_d[2] %v (%h) != %v (%h)", - #procedure, vs_d[2], vs_d[2], - 0hbfbc5da6a4441787, 0hbfbc5da6a4441787)) - tc.expect(t, vs_d[3] == 0h4010074fb549f948, fmt.tprintf("%v: vs_d[3] %v (%h) != %v (%h)", - #procedure, vs_d[3], vs_d[3], - 0h4010074fb549f948, 0h4010074fb549f948)) - tc.expect(t, vs_d[1587] == 0h400befa82e87d2c7, fmt.tprintf("%v: vs_d[1587] %v (%h) != %v (%h)", - #procedure, vs_d[1587], vs_d[1587], - 0h400befa82e87d2c7, 0h400befa82e87d2c7)) - tc.expect(t, vs_d[1588] == 2.83457, fmt.tprintf("%v: vs_d[1588] %v (%h) != %v (%h)", - #procedure, vs_d[1588], vs_d[1588], 2.83457, 2.83457)) - tc.expect(t, vs_d[1589] == -1.56121, fmt.tprintf("%v: vs_d[1589] %v (%h) != %v (%h)", - #procedure, vs_d[1589], vs_d[1589], -1.56121, -1.56121)) + testing.expectf(t, vs_d_ok, "vs_d_ok %v != %v", vs_d_ok, true) + testing.expectf(t, len(vs_d) == 1590, "len(vs_d) %v != %v", len(vs_d), 1590) + testing.expectf(t, vs_d[0] == 4.06266, "vs_d[0] %v (%h) != %v (%h)", vs_d[0], vs_d[0], 4.06266, 4.06266) + testing.expectf(t, vs_d[1] == 2.83457, "vs_d[1] %v (%h) != %v (%h)", vs_d[1], vs_d[1], 2.83457, 2.83457) + testing.expectf(t, vs_d[2] == 0hbfbc5da6a4441787, "vs_d[2] %v (%h) != %v (%h)", vs_d[2], vs_d[2], 0hbfbc5da6a4441787, 0hbfbc5da6a4441787) + testing.expectf(t, vs_d[3] == 0h4010074fb549f948, "vs_d[3] %v (%h) != %v (%h)", vs_d[3], vs_d[3], 0h4010074fb549f948, 0h4010074fb549f948) + testing.expectf(t, vs_d[1587] == 0h400befa82e87d2c7, "vs_d[1587] %v (%h) != %v (%h)", vs_d[1587], vs_d[1587], 0h400befa82e87d2c7, 0h400befa82e87d2c7) + testing.expectf(t, vs_d[1588] == 2.83457, "vs_d[1588] %v (%h) != %v (%h)", vs_d[1588], vs_d[1588], 2.83457, 2.83457) + testing.expectf(t, vs_d[1589] == -1.56121, "vs_d[1589] %v (%h) != %v (%h)", vs_d[1589], vs_d[1589], -1.56121, -1.56121) /* Corner stack */ - tc.expect(t, len(v.corner_stack) == 1, - fmt.tprintf("%v: len(v.corner_stack) %v != %v", #procedure, len(v.corner_stack), 1)) + testing.expectf(t, len(v.corner_stack) == 1, "len(v.corner_stack) %v != %v", len(v.corner_stack), 1) { e := "reference" - tc.expect(t, v.corner_stack[0].name == e, fmt.tprintf("%v: v.corner_stack[0].name %v != %v", - #procedure, v.corner_stack[0].name, e)) + testing.expectf(t, v.corner_stack[0].name == e, "v.corner_stack[0].name %v != %v", v.corner_stack[0].name, e) } - tc.expect(t, v.corner_stack[0].components == 1, fmt.tprintf("%v: v.corner_stack[0].components %v != %v", - #procedure, v.corner_stack[0].components, 1)) + testing.expectf(t, v.corner_stack[0].components == 1, "v.corner_stack[0].components %v != %v", v.corner_stack[0].components, 1) /* Corner stack data */ cs_d, cs_d_ok := v.corner_stack[0].data.([]i32le) - tc.expect(t, cs_d_ok, fmt.tprintf("%v: cs_d_ok %v != %v", #procedure, cs_d_ok, true)) - tc.expect(t, len(cs_d) == 2026, fmt.tprintf("%v: len(cs_d) %v != %v", #procedure, len(cs_d), 2026)) - tc.expect(t, cs_d[0] == 6, fmt.tprintf("%v: cs_d[0] %v != %v", #procedure, cs_d[0], 6)) - tc.expect(t, cs_d[2025] == -32, fmt.tprintf("%v: cs_d[2025] %v != %v", #procedure, cs_d[2025], -32)) + testing.expectf(t, cs_d_ok, "cs_d_ok %v != %v", cs_d_ok, true) + testing.expectf(t, len(cs_d) == 2026, "len(cs_d) %v != %v", len(cs_d), 2026) + testing.expectf(t, cs_d[0] == 6, "cs_d[0] %v != %v", cs_d[0], 6) + testing.expectf(t, cs_d[2025] == -32, "cs_d[2025] %v != %v", cs_d[2025], -32) /* Edge and face stacks (empty) */ - tc.expect(t, len(v.edge_stack) == 0, fmt.tprintf("%v: len(v.edge_stack) %v != %v", - #procedure, len(v.edge_stack), 0)) - tc.expect(t, len(v.face_stack) == 0, fmt.tprintf("%v: len(v.face_stack) %v != %v", - #procedure, len(v.face_stack), 0)) + testing.expectf(t, len(v.edge_stack) == 0, "len(v.edge_stack) %v != %v", len(v.edge_stack), 0) + testing.expectf(t, len(v.face_stack) == 0, "len(v.face_stack) %v != %v", len(v.face_stack), 0) } @test @@ -154,72 +126,72 @@ test_write :: proc(t: ^testing.T) { n, write_err := hxa.write(buf, w_file) write_e :: hxa.Write_Error.None - tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e)) - tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size)) + testing.expectf(t, write_err == write_e, fmt.tprintf("write_err %v != %v", write_err, write_e)) + testing.expectf(t, n == required_size, fmt.tprintf("n %v != %v", n, required_size)) file, read_err := hxa.read(buf) read_e :: hxa.Read_Error.None - tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e)) + testing.expectf(t, read_err == read_e, fmt.tprintf("read_err %v != %v", read_err, read_e)) defer hxa.file_destroy(file) - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 3, fmt.tprintf("%v: file.version %v != %v", #procedure, file.version, 3)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, fmt.tprintf("file.magic_number %v != %v", + file.magic_number, 0x417848)) + testing.expectf(t, file.version == 3, fmt.tprintf("file.version %v != %v", file.version, 3)) + testing.expectf(t, file.internal_node_count == 1, fmt.tprintf("file.internal_node_count %v != %v", + file.internal_node_count, 1)) - tc.expect(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("%v: len(file.nodes) %v != %v", - #procedure, len(file.nodes), len(w_file.nodes))) + testing.expectf(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("len(file.nodes) %v != %v", + len(file.nodes), len(w_file.nodes))) m := &file.nodes[0].meta_data w_m := &w_file.nodes[0].meta_data - tc.expect(t, len(m^) == len(w_m^), fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), len(w_m^))) - tc.expect(t, m[0].name == w_m[0].name, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, w_m[0].name)) + testing.expectf(t, len(m^) == len(w_m^), fmt.tprintf("len(m^) %v != %v", len(m^), len(w_m^))) + testing.expectf(t, m[0].name == w_m[0].name, fmt.tprintf("m[0].name %v != %v", m[0].name, w_m[0].name)) m_v, m_v_ok := m[0].value.([]f64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v: %v != len(m_v) %v", - #procedure, len(m_v), len(n1_m1_value))) + testing.expectf(t, m_v_ok, fmt.tprintf("m_v_ok %v != %v", m_v_ok, true)) + testing.expectf(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v != len(m_v) %v", + len(m_v), len(n1_m1_value))) for i := 0; i < len(m_v); i += 1 { - tc.expect(t, m_v[i] == n1_m1_value[i], fmt.tprintf("%v: m_v[%d] %v != %v", - #procedure, i, m_v[i], n1_m1_value[i])) + testing.expectf(t, m_v[i] == n1_m1_value[i], fmt.tprintf("m_v[%d] %v != %v", + i, m_v[i], n1_m1_value[i])) } v, v_ok := file.nodes[0].content.(hxa.Node_Image) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) - tc.expect(t, v.type == n1_content.type, fmt.tprintf("%v: v.type %v != %v", #procedure, v.type, n1_content.type)) - tc.expect(t, len(v.resolution) == 3, fmt.tprintf("%v: len(v.resolution) %v != %v", - #procedure, len(v.resolution), 3)) - tc.expect(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("%v: len(v.image_stack) %v != %v", - #procedure, len(v.image_stack), len(n1_content.image_stack))) + testing.expectf(t, v_ok, fmt.tprintf("v_ok %v != %v", v_ok, true)) + testing.expectf(t, v.type == n1_content.type, fmt.tprintf("v.type %v != %v", v.type, n1_content.type)) + testing.expectf(t, len(v.resolution) == 3, fmt.tprintf("len(v.resolution) %v != %v", + len(v.resolution), 3)) + testing.expectf(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("len(v.image_stack) %v != %v", + len(v.image_stack), len(n1_content.image_stack))) for i := 0; i < len(v.image_stack); i += 1 { - tc.expect(t, v.image_stack[i].name == n1_content.image_stack[i].name, - fmt.tprintf("%v: v.image_stack[%d].name %v != %v", - #procedure, i, v.image_stack[i].name, n1_content.image_stack[i].name)) - tc.expect(t, v.image_stack[i].components == n1_content.image_stack[i].components, - fmt.tprintf("%v: v.image_stack[%d].components %v != %v", - #procedure, i, v.image_stack[i].components, n1_content.image_stack[i].components)) + testing.expectf(t, v.image_stack[i].name == n1_content.image_stack[i].name, + fmt.tprintf("v.image_stack[%d].name %v != %v", + i, v.image_stack[i].name, n1_content.image_stack[i].name)) + testing.expectf(t, v.image_stack[i].components == n1_content.image_stack[i].components, + fmt.tprintf("v.image_stack[%d].components %v != %v", + i, v.image_stack[i].components, n1_content.image_stack[i].components)) switch n1_t in n1_content.image_stack[i].data { case []u8: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []u8", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []u8", #procedure)) case []i32le: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []i32le", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []i32le", #procedure)) case []f32le: l, l_ok := v.image_stack[i].data.([]f32le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v (%h) != %v (%h)", - #procedure, j, l[j], l[j], n1_t[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v (%h) != %v (%h)", + j, l[j], l[j], n1_t[j], n1_t[j])) } case []f64le: l, l_ok := v.image_stack[i].data.([]f64le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v != %v", #procedure, j, l[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v != %v", j, l[j], n1_t[j])) } } } -} +} \ No newline at end of file From 601df0e8f77bb9da13557e9f54abbd99b973c4f2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 14:36:36 +0200 Subject: [PATCH 052/151] Port `tests\core\encoding\json` --- core/encoding/json/marshal.odin | 4 +- core/encoding/json/parser.odin | 65 ++++++++------- core/encoding/json/types.odin | 14 ++-- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/json/test_core_json.odin | 88 +++++--------------- 6 files changed, 67 insertions(+), 108 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 4f5b50ec5..2933adf9a 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -62,8 +62,8 @@ Marshal_Options :: struct { mjson_skipped_first_braces_end: bool, } -marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) { - b := strings.builder_make(allocator) +marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) { + b := strings.builder_make(allocator, loc) defer if err != nil { strings.builder_destroy(&b) } diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index 3973725dc..38f71edf6 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par } -parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { - return parse_string(string(data), spec, parse_integers, allocator) +parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { + return parse_string(string(data), spec, parse_integers, allocator, loc) } -parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { +parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { context.allocator = allocator p := make_parser_from_string(data, spec, parse_integers, allocator) switch p.spec { case .JSON: - return parse_object(&p) + return parse_object(&p, loc) case .JSON5: - return parse_value(&p) + return parse_value(&p, loc) case .SJSON: #partial switch p.curr_token.kind { case .Ident, .String: - return parse_object_body(&p, .EOF) + return parse_object_body(&p, .EOF, loc) } - return parse_value(&p) + return parse_value(&p, loc) } - return parse_object(&p) + return parse_object(&p, loc) } token_end_pos :: proc(tok: Token) -> Pos { @@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) { return false } -parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None token := p.curr_token #partial switch token.kind { @@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { case .String: advance_token(p) - return unquote_string(token, p.spec, p.allocator) + return unquote_string(token, p.spec, p.allocator, loc) case .Open_Brace: - return parse_object(p) + return parse_object(p, loc) case .Open_Bracket: - return parse_array(p) + return parse_array(p, loc) case: if p.spec != .JSON { @@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { return } -parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None expect_token(p, .Open_Bracket) or_return @@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { array.allocator = p.allocator defer if err != nil { for elem in array { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(array) + delete(array, loc) } for p.curr_token.kind != .Close_Bracket { - elem := parse_value(p) or_return - append(&array, elem) + elem := parse_value(p, loc) or_return + append(&array, elem, loc) if parse_comma(p) { break @@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio return } -parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) { +parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) { tok := p.curr_token if p.spec != .JSON { if allow_token(p, .Ident) { - return clone_string(tok.text, key_allocator) + return clone_string(tok.text, key_allocator, loc) } } if tok_err := expect_token(p, .String); tok_err != nil { err = .Expected_String_For_Object_Key return } - return unquote_string(tok, p.spec, key_allocator) + return unquote_string(tok, p.spec, key_allocator, loc) } -parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) { - obj.allocator = p.allocator +parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) { + obj = make(Object, allocator=p.allocator, loc=loc) + defer if err != nil { for key, elem in obj { - delete(key, p.allocator) - destroy_value(elem) + delete(key, p.allocator, loc) + destroy_value(elem, loc=loc) } - delete(obj) + delete(obj, loc) } for p.curr_token.kind != end_token { - key := parse_object_key(p, p.allocator) or_return + key := parse_object_key(p, p.allocator, loc) or_return parse_colon(p) or_return - elem := parse_value(p) or_return + elem := parse_value(p, loc) or_return if key in obj { err = .Duplicate_Object_Key - delete(key, p.allocator) + delete(key, p.allocator, loc) return } @@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er // inserting empty key/values into the object and for those we do not // want to allocate anything if key != "" { - reserve_error := reserve(&obj, len(obj) + 1) + reserve_error := reserve(&obj, len(obj) + 1, loc) if reserve_error == mem.Allocator_Error.Out_Of_Memory { return nil, .Out_Of_Memory } @@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er return obj, .None } -parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { expect_token(p, .Open_Brace) or_return - obj := parse_object_body(p, .Close_Brace) or_return + obj := parse_object_body(p, .Close_Brace, loc) or_return expect_token(p, .Close_Brace) or_return return obj, .None } @@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a } return string(b[:w]), nil -} +} \ No newline at end of file diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 73e183615..41eb21377 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -89,22 +89,22 @@ Error :: enum { -destroy_value :: proc(value: Value, allocator := context.allocator) { +destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) { context.allocator = allocator #partial switch v in value { case Object: for key, elem in v { - delete(key) - destroy_value(elem) + delete(key, loc=loc) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case Array: for elem in v { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case String: - delete(v) + delete(v, loc=loc) } } diff --git a/tests/core/Makefile b/tests/core/Makefile index 46b9e352e..98027ab39 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -49,7 +49,7 @@ encoding_test: $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa - $(ODIN) run encoding/json $(COMMON) -out:test_json + $(ODIN) test encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml diff --git a/tests/core/build.bat b/tests/core/build.bat index 094a12f14..4bc5bb938 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -30,7 +30,7 @@ echo --- %PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b +%PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 813d11b2c..92c050952 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -2,46 +2,8 @@ package test_core_json import "core:encoding/json" import "core:testing" -import "core:fmt" -import "core:os" import "core:mem/virtual" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - parse_json(&t) - marshal_json(&t) - unmarshal_json(&t) - surrogate(&t) - utf8_string_of_multibyte_characters(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test parse_json :: proc(t: ^testing.T) { @@ -72,10 +34,9 @@ parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) - - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) } @test @@ -83,7 +44,7 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { arena: virtual.Arena arena_buffer: [256]byte arena_init_error := virtual.arena_init_buffer(&arena, arena_buffer[:]) - testing.expect(t, arena_init_error == nil, fmt.tprintf("Expected arena initialization to not return error, got: %v\n", arena_init_error)) + testing.expectf(t, arena_init_error == nil, "Expected arena initialization to not return error, got: %v\n", arena_init_error) context.allocator = virtual.arena_allocator(&arena) @@ -114,11 +75,11 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) expected_error := json.Error.Out_Of_Memory - msg := fmt.tprintf("Expected `json.parse` to fail with %v, got %v", expected_error, err) - expect(t, err == json.Error.Out_Of_Memory, msg) + testing.expectf(t, err == json.Error.Out_Of_Memory, "Expected `json.parse` to fail with %v, got %v", expected_error, err) } @test @@ -134,9 +95,9 @@ marshal_json :: proc(t: ^testing.T) { b = 5, } - _, err := json.marshal(my_struct) - msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err) - expect(t, err == nil, msg) + data, err := json.marshal(my_struct) + defer delete(data) + testing.expectf(t, err == nil, "Expected `json.marshal` to return nil, got %v", err) } PRODUCTS := ` @@ -378,17 +339,12 @@ unmarshal_json :: proc(t: ^testing.T) { err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION) defer cleanup(g) - msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err) - expect(t, err == nil, msg) - - msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) - expect(t, len(g.products) == len(original_data.products), msg) - - msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) - expect(t, original_data.cash == g.cash, msg) + testing.expectf(t, err == nil, "Expected `json.unmarshal` to return nil, got %v", err) + testing.expectf(t, len(g.products) == len(original_data.products), "Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) + testing.expectf(t, original_data.cash == g.cash, "Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) for p, i in g.products { - expect(t, p == original_data.products[i], "Producted unmarshaled improperly") + testing.expect(t, p == original_data.products[i], "Producted unmarshaled improperly") } } @@ -397,17 +353,19 @@ surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` out, err := json.marshal(input) - expect(t, err == nil, fmt.tprintf("Expected `json.marshal(%q)` to return a nil error, got %v", input, err)) + defer delete(out) + testing.expectf(t, err == nil, "Expected `json.marshal(%q)` to return a nil error, got %v", input, err) back: string uerr := json.unmarshal(out, &back) - expect(t, uerr == nil, fmt.tprintf("Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr)) - expect(t, back == input, fmt.tprintf("Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr)) + defer delete(back) + testing.expectf(t, uerr == nil, "Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr) + testing.expectf(t, back == input, "Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr) } @test utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { - _, err := json.parse_string(`"🐛✅"`) - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) -} + val, err := json.parse_string(`"🐛✅"`) + defer json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} \ No newline at end of file From 6641a6f6c9357c8cb977622ff2da2937af69cfed Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 14:57:43 +0200 Subject: [PATCH 053/151] Port `tests\core\encoding\varint` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- .../encoding/varint/test_core_varint.odin | 84 ++++++------------- 3 files changed, 26 insertions(+), 62 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 98027ab39..4dc12969d 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -50,7 +50,7 @@ encoding_test: $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) test encoding/json $(COMMON) -out:test_json - $(ODIN) run encoding/varint $(COMMON) -out:test_varint + $(ODIN) test encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml filepath_test: diff --git a/tests/core/build.bat b/tests/core/build.bat index 4bc5bb938..983546ddb 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -31,7 +31,7 @@ echo --- %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b +%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- diff --git a/tests/core/encoding/varint/test_core_varint.odin b/tests/core/encoding/varint/test_core_varint.odin index ee1798aa7..5058f3022 100644 --- a/tests/core/encoding/varint/test_core_varint.odin +++ b/tests/core/encoding/varint/test_core_varint.odin @@ -2,110 +2,74 @@ package test_core_varint import "core:encoding/varint" import "core:testing" -import "core:fmt" -import "core:os" import "core:slice" import "core:math/rand" -TEST_count := 0 -TEST_fail := 0 - -RANDOM_TESTS :: 100 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_leb128(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +NUM_RANDOM_TESTS_PER_BYTE_SIZE :: 10_000 @(test) -test_leb128 :: proc(t: ^testing.T) { +test_uleb :: proc(t: ^testing.T) { buf: [varint.LEB128_MAX_BYTES]u8 for vector in ULEB_Vectors { val, size, err := varint.decode_uleb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) if err == .None { // Try to roundtrip size, err = varint.encode_uleb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_ileb :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for vector in ILEB_Vectors { val, size, err := varint.decode_ileb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v", vector.error, err) if err == .None { // Try to roundtrip size, err = varint.encode_ileb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_random :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for num_bytes in 1..=uint(16) { - for _ in 0..=RANDOM_TESTS { + for _ in 0..=NUM_RANDOM_TESTS_PER_BYTE_SIZE { unsigned, signed := get_random(num_bytes) - { encode_size, encode_err := varint.encode_uleb128(buf[:], unsigned) - msg := fmt.tprintf("%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) decoded, decode_size, decode_err := varint.decode_uleb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) - expect(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, "Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) } { encode_size, encode_err := varint.encode_ileb128(buf[:], signed) - msg := fmt.tprintf("%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) decoded, decode_size, decode_err := varint.decode_ileb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) - expect(t, decode_err == .None && decode_size == encode_size && decoded == signed, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == signed, "Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) } } } } +@(private) get_random :: proc(byte_count: uint) -> (u: u128, i: i128) { assert(byte_count >= 0 && byte_count <= size_of(u128)) From 9d0f4833bfde5c1ae443c4826f268b565022a817 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 15:45:15 +0200 Subject: [PATCH 054/151] Port `tests\core\encoding\xml` Made them run in parallel as well. --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/xml/test_core_xml.odin | 226 ++++++++------------- 3 files changed, 89 insertions(+), 141 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4dc12969d..357a22edb 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -51,7 +51,7 @@ encoding_test: $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) test encoding/json $(COMMON) -out:test_json $(ODIN) test encoding/varint $(COMMON) -out:test_varint - $(ODIN) run encoding/xml $(COMMON) -out:test_xml + $(ODIN) test encoding/xml $(COMMON) -out:test_xml filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath diff --git a/tests/core/build.bat b/tests/core/build.bat index 983546ddb..d35fee5b3 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -32,7 +32,7 @@ echo --- %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b +%PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- echo Running core:fmt tests diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index c62033491..22852d1f3 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -2,10 +2,10 @@ package test_core_xml import "core:encoding/xml" import "core:testing" -import "core:mem" import "core:strings" import "core:io" import "core:fmt" +import "core:log" import "core:hash" Silent :: proc(pos: xml.Pos, format: string, args: ..any) {} @@ -14,9 +14,6 @@ OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, }, expected_doctype = "", } -TEST_count := 0 -TEST_fail := 0 - TEST :: struct { filename: string, options: xml.Options, @@ -24,22 +21,14 @@ TEST :: struct { crc32: u32, } -/* - Relative to ODIN_ROOT -*/ -TEST_FILE_PATH_PREFIX :: "tests/core/assets" +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/" -TESTS :: []TEST{ - /* - First we test that certain files parse without error. - */ - - { - /* - Tests UTF-8 idents and values. - Test namespaced ident. - Tests that nested partial CDATA start doesn't trip up parser. - */ +@(test) +xml_test_utf8_normal :: proc(t: ^testing.T) { + run_test(t, { + // Tests UTF-8 idents and values. + // Test namespaced ident. + // Tests that nested partial CDATA start doesn't trip up parser. filename = "XML/utf8.xml", options = { flags = { @@ -48,13 +37,14 @@ TESTS :: []TEST{ expected_doctype = "恥ずべきフクロウ", }, crc32 = 0xe9b62f03, - }, + }) +} - { - /* - Same as above. - Unbox CDATA in data tag. - */ +@(test) +xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) { + run_test(t, { + // Same as above. + // Unbox CDATA in data tag. filename = "XML/utf8.xml", options = { flags = { @@ -63,13 +53,14 @@ TESTS :: []TEST{ expected_doctype = "恥ずべきフクロウ", }, crc32 = 0x9c2643ed, - }, + }) +} - { - /* - Simple Qt TS translation file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_nl_qt_ts :: proc(t: ^testing.T) { + run_test(t, { + // Simple Qt TS translation file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-qt-ts.ts", options = { flags = { @@ -78,13 +69,14 @@ TESTS :: []TEST{ expected_doctype = "TS", }, crc32 = 0x859b7443, - }, + }) +} - { - /* - Simple XLiff 1.2 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_1_2 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 1.2 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-1.2.xliff", options = { flags = { @@ -93,13 +85,14 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x3deaf329, - }, + }) +} - { - /* - Simple XLiff 2.0 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_2_0 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 2.0 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-2.0.xliff", options = { flags = { @@ -108,9 +101,12 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x0c55e287, - }, + }) +} - { +@(test) +xml_test_entities :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -119,9 +115,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x05373317, - }, + }) +} - { +@(test) +xml_test_entities_unbox :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -130,9 +129,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x3b6d4a90, - }, + }) +} - { +@(test) +xml_test_entities_unbox_decode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -141,12 +143,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x5be2ffdc, - }, + }) +} - /* - Then we test that certain errors are returned as expected. - */ - { +@(test) +xml_test_invalid_doctype :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/utf8.xml", options = { flags = { @@ -156,12 +158,12 @@ TESTS :: []TEST{ }, err = .Invalid_DocType, crc32 = 0x49b83d0a, - }, + }) +} - /* - Parse the 9.08 MiB unicode.xml for good measure. - */ - { +@(test) +xml_test_unicode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/unicode.xml", options = { flags = { @@ -171,39 +173,37 @@ TESTS :: []TEST{ }, err = .None, crc32 = 0x0b6100ab, - }, + }) } -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] LOG:\n\t%v\n", loc, v) +@(private) +run_test :: proc(t: ^testing.T, test: TEST) { + path := strings.concatenate({TEST_SUITE_PATH, test.filename}) + defer delete(path) + + doc, err := xml.load_from_file(path, test.options, Silent) + defer xml.destroy(doc) + + tree_string := doc_to_string(doc) + tree_bytes := transmute([]u8)tree_string + defer delete(tree_bytes) + + crc32 := hash.crc32(tree_bytes) + + failed := err != test.err + testing.expectf(t, err == test.err, "%v: Expected return value %v, got %v", test.filename, test.err, err) + + failed |= crc32 != test.crc32 + testing.expectf(t, crc32 == test.crc32, "%v: Expected CRC 0x%08x, got 0x%08x, with options %v", test.filename, test.crc32, crc32, test.options) + + if failed { + // Don't fully print big trees. + tree_string = tree_string[:min(2_048, len(tree_string))] + log.error(tree_string) } } -test_file_path :: proc(filename: string) -> (path: string) { - - path = fmt.tprintf("%v%v/%v", ODIN_ROOT, TEST_FILE_PATH_PREFIX, filename) - temp := transmute([]u8)path - - for r, i in path { - if r == '\\' { - temp[i] = '/' - } - } - return path -} - +@(private) doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { /* Effectively a clone of the debug printer in the xml package. @@ -284,56 +284,4 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { print(strings.to_writer(&buf), doc) return strings.clone(strings.to_string(buf)) -} - -@test -run_tests :: proc(t: ^testing.T) { - for test in TESTS { - path := test_file_path(test.filename) - log(t, fmt.tprintf("Trying to parse %v", path)) - - doc, err := xml.load_from_file(path, test.options, Silent) - defer xml.destroy(doc) - - tree_string := doc_to_string(doc) - tree_bytes := transmute([]u8)tree_string - defer delete(tree_bytes) - - crc32 := hash.crc32(tree_bytes) - - failed := err != test.err - err_msg := fmt.tprintf("Expected return value %v, got %v", test.err, err) - expect(t, err == test.err, err_msg) - - failed |= crc32 != test.crc32 - err_msg = fmt.tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options) - expect(t, crc32 == test.crc32, err_msg) - - if failed { - /* - Don't fully print big trees. - */ - tree_string = tree_string[:min(2_048, len(tree_string))] - fmt.println(tree_string) - } - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - run_tests(&t) - - if len(track.allocation_map) > 0 { - for _, v in track.allocation_map { - err_msg := fmt.tprintf("%v Leaked %v bytes.", v.location, v.size) - expect(&t, false, err_msg) - } - } - - fmt.printf("\n%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) } \ No newline at end of file From d334b8c72a6c95e22af2113a4dc71c04224af4ee Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 16:04:24 +0200 Subject: [PATCH 055/151] Port `tests\core\path\filepath` --- tests/core/Makefile | 2 +- tests/core/build.bat | 10 +-- .../path/filepath/test_core_filepath.odin | 73 +++++++++---------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 357a22edb..df3924e4d 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -54,7 +54,7 @@ encoding_test: $(ODIN) test encoding/xml $(COMMON) -out:test_xml filepath_test: - $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath + $(ODIN) test path/filepath $(COMMON) -out:test_core_filepath fmt_test: $(ODIN) run fmt $(COMMON) -out:test_core_fmt diff --git a/tests/core/build.bat b/tests/core/build.bat index d35fee5b3..214f15b45 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -34,6 +34,11 @@ echo --- %PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b +echo --- +echo Running core:path/filepath tests +echo --- +%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b + echo --- echo Running core:fmt tests echo --- @@ -74,11 +79,6 @@ echo Running core:odin tests echo --- %PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b -echo --- -echo Running core:path/filepath tests -echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b - echo --- echo Running core:reflect tests echo --- diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin index 4c70e5f28..94b9329bb 100644 --- a/tests/core/path/filepath/test_core_filepath.odin +++ b/tests/core/path/filepath/test_core_filepath.odin @@ -1,26 +1,19 @@ // Tests "path.odin" in "core:path/filepath". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/path/filepath/test_core_filepath.odin -collection:tests=tests package test_core_filepath import "core:fmt" import "core:path/filepath" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} +@(test) +test_split_list :: proc(t: ^testing.T) { when ODIN_OS == .Windows { - test_split_list_windows(&t) + test_split_list_windows(t) } else { - test_split_list_unix(&t) + test_split_list_unix(t) } - - tc.report(&t) } -@test test_split_list_windows :: proc(t: ^testing.T) { Datum :: struct { i: int, @@ -41,12 +34,12 @@ test_split_list_windows :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", + defer delete_split(r) + testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e))) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", + testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", i, #procedure, d.v, r[j], j, d.e[j])) } } @@ -55,47 +48,43 @@ test_split_list_windows :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + defer delete_split(r) + testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) } } } -@test test_split_list_unix :: proc(t: ^testing.T) { Datum :: struct { - i: int, v: string, e: [3]string, } @static data := []Datum{ - { 0, "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", + { "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537 - { 1, "a::b", [3]string{"a", "", "b"} }, - { 2, "a:b:", [3]string{"a", "b", ""} }, - { 3, ":a:b", [3]string{"", "a", "b"} }, - { 4, "::", [3]string{"", "", ""} }, - { 5, "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, - { 6, "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, + { "a::b", [3]string{"a", "", "b"} }, + { "a:b:", [3]string{"a", "b", ""} }, + { ":a:b", [3]string{"", "a", "b"} }, + { "::", [3]string{"", "", ""} }, + { "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, + { "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, } - for d, i in data { - assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + for d in data { r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", - i, #procedure, d.v, len(r), len(d.e))) + defer delete_split(r) + testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e)) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", - i, #procedure, d.v, r[j], j, d.e[j])) + testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j]) } } } @@ -103,15 +92,23 @@ test_split_list_unix :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r)) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0]) } } } + +@(private) +delete_split :: proc(s: []string) { + for part in s { + delete(part) + } + delete(s) +} \ No newline at end of file From 6f7c5a7577c6c03a6894728009daa7681e60a0a1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 16:52:29 +0200 Subject: [PATCH 056/151] Port `tests\core\fmt` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/fmt/test_core_fmt.odin | 61 ++++++++++--------------------- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index df3924e4d..bc574b004 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -57,7 +57,7 @@ filepath_test: $(ODIN) test path/filepath $(COMMON) -out:test_core_filepath fmt_test: - $(ODIN) run fmt $(COMMON) -out:test_core_fmt + $(ODIN) test fmt $(COMMON) -out:test_core_fmt hash_test: $(ODIN) run hash $(COMMON) -o:speed -out:test_hash diff --git a/tests/core/build.bat b/tests/core/build.bat index 214f15b45..aa307672b 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:fmt tests echo --- -%PATH_TO_ODIN% run fmt %COMMON% %COLLECTION% -out:test_core_fmt.exe || exit /b +%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b echo --- echo Running core:hash tests diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 82d009ac6..3e5839ae7 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -1,47 +1,8 @@ package test_core_fmt import "core:fmt" -import "core:os" -import "core:testing" import "core:mem" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_fmt_memory(&t) - test_fmt_doc_examples(&t) - test_fmt_options(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { - got := fmt.tprintf(format, ..args) - expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc) -} +import "core:testing" @(test) test_fmt_memory :: proc(t: ^testing.T) { @@ -75,7 +36,7 @@ test_fmt_doc_examples :: proc(t: ^testing.T) { } @(test) -test_fmt_options :: proc(t: ^testing.T) { +test_fmt_escaping_prefixes :: proc(t: ^testing.T) { // Escaping check(t, "% { } 0 { } } {", "%% {{ }} {} {{ }} }} {{", 0 ) @@ -86,7 +47,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "+3", "%+i", 3 ) check(t, "0b11", "%#b", 3 ) check(t, "0xA", "%#X", 10 ) +} +@(test) +test_fmt_indexing :: proc(t: ^testing.T) { // Specific index formatting check(t, "1 2 3", "%i %i %i", 1, 2, 3) check(t, "1 2 3", "%[0]i %[1]i %[2]i", 1, 2, 3) @@ -95,7 +59,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "1 2 3", "%i %[1]i %i", 1, 2, 3) check(t, "1 3 2", "%i %[2]i %i", 1, 2, 3) check(t, "1 1 1", "%[0]i %[0]i %[0]i", 1) +} +@(test) +test_fmt_width_precision :: proc(t: ^testing.T) { // Width check(t, "3.140", "%f", 3.14) check(t, "3.140", "%4f", 3.14) @@ -133,7 +100,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "3.140", "%*[1].*[2][0]f", 3.14, 5, 3) check(t, "3.140", "%*[2].*[1]f", 3.14, 3, 5) check(t, "3.140", "%5.*[1]f", 3.14, 3) +} +@(test) +test_fmt_arg_errors :: proc(t: ^testing.T) { // Error checking check(t, "%!(MISSING ARGUMENT)%!(NO VERB)", "%" ) @@ -156,7 +126,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)%!(EXTRA 0)", "%[1]", 0) check(t, "3.1%!(EXTRA 3.14)", "%.1f", 3.14, 3.14) +} +@(test) +test_fmt_python_syntax :: proc(t: ^testing.T) { // Python-like syntax check(t, "1 2 3", "{} {} {}", 1, 2, 3) check(t, "3 2 1", "{2} {1} {0}", 1, 2, 3) @@ -181,3 +154,9 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{", 1) check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{0", 1 ) } + +@(private) +check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) { + got := fmt.tprintf(format, ..args) + testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp) +} \ No newline at end of file From 39fd73fe171c06f9eb58e107d5d5242114b43539 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 18:05:09 +0200 Subject: [PATCH 057/151] Port `testing\core\hash` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/hash/test_core_hash.odin | 604 +++++++++++------------ tests/core/hash/test_vectors_xxhash.odin | 6 +- 4 files changed, 302 insertions(+), 312 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index bc574b004..873bd24af 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -60,7 +60,7 @@ fmt_test: $(ODIN) test fmt $(COMMON) -out:test_core_fmt hash_test: - $(ODIN) run hash $(COMMON) -o:speed -out:test_hash + $(ODIN) test hash $(COMMON) -o:speed -out:test_hash i18n_test: $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n diff --git a/tests/core/build.bat b/tests/core/build.bat index aa307672b..4748c3071 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -47,7 +47,7 @@ echo --- echo --- echo Running core:hash tests echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b echo --- echo Running core:image tests diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index 932d2f34c..a6294de55 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -5,47 +5,314 @@ import "core:hash" import "core:time" import "core:testing" import "core:fmt" -import "core:os" +import "core:log" import "core:math/rand" import "base:intrinsics" -TEST_count := 0 -TEST_fail := 0 +@test +test_xxhash_zero_fixed :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return + // All at once. + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + xxh32 := xxhash.XXH32(b) + xxh64 := xxhash.XXH64(b) + xxh3_64 := xxhash.XXH3_64(b) + xxh3_128 := xxhash.XXH3_128(b) + + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + } +} + +@(test) +test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) + + // Streamed + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() + defer xxhash.XXH32_destroy_state(xxh_32_state) + testing.expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state") + + xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() + defer xxhash.XXH64_destroy_state(xxh_64_state) + testing.expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state") + + xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_64_state) + testing.expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state") + + xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_128_state) + testing.expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state") + + // XXH3_128_update + random_seed := rand.create(t.seed) + for len(b) > 0 { + update_size := min(len(b), rand.int_max(8192, &random_seed)) + if update_size > 4096 { + update_size %= 73 + } + xxhash.XXH32_update (xxh_32_state, b[:update_size]) + xxhash.XXH64_update (xxh_64_state, b[:update_size]) + + xxhash.XXH3_64_update (xxh3_64_state, b[:update_size]) + xxhash.XXH3_128_update(xxh3_128_state, b[:update_size]) + + b = b[update_size:] + } + + // Now finalize + xxh32 := xxhash.XXH32_digest(xxh_32_state) + xxh64 := xxhash.XXH64_digest(xxh_64_state) + + xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) + xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) + + xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + + testing.expect(t, xxh32 == v.xxh_32, xxh32_error) + testing.expect(t, xxh64 == v.xxh_64, xxh64_error) + testing.expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) + testing.expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + } +} + +@test +test_xxhash_seeded :: proc(t: ^testing.T) { + buf := make([]u8, 256) + defer delete(buf) + + for seed, table in XXHASH_TEST_VECTOR_SEEDED { + for v, i in table { + b := buf[:i] + + xxh32 := xxhash.XXH32(b, u32(seed)) + xxh64 := xxhash.XXH64(b, seed) + xxh3_64 := xxhash.XXH3_64(b, seed) + xxh3_128 := xxhash.XXH3_128(b, seed) + + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + + if len(b) > xxhash.XXH3_MIDSIZE_MAX { + xxh3_state, _ := xxhash.XXH3_create_state() + xxhash.XXH3_64_reset_with_seed(xxh3_state, seed) + xxhash.XXH3_64_update(xxh3_state, b) + xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state) + xxhash.XXH3_destroy_state(xxh3_state) + testing.expectf(t, xxh3_64_streamed == v.xxh3_64, "[XXH3_64s(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64_streamed) + + xxh3_state2, _ := xxhash.XXH3_create_state() + xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed) + xxhash.XXH3_128_update(xxh3_state2, b) + xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2) + xxhash.XXH3_destroy_state(xxh3_state2) + testing.expectf(t, xxh3_128_streamed == v.xxh3_128, "[XXH3_128s(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128_streamed) + } } } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) +} + +@test +test_xxhash_secret :: proc(t: ^testing.T) { + buf := make([]u8, 256) + defer delete(buf) + + for secret, table in XXHASH_TEST_VECTOR_SECRET { + secret_bytes := transmute([]u8)secret + for v, i in table { + b := buf[:i] + + xxh3_128 := xxhash.XXH3_128(b, secret_bytes) + testing.expectf(t, xxh3_128 == v.xxh3_128_secret, "[XXH3_128(%03d)] Expected: %32x, got: %32x", i, v.xxh3_128_secret, xxh3_128) + } } } -main :: proc() { - t := testing.T{} - test_benchmark_runner(&t) - test_crc64_vectors(&t) - test_xxhash_vectors(&t) - test_xxhash_large(&t) +@test +test_crc64_vectors :: proc(t: ^testing.T) { + vectors := map[string][4]u64 { + "123456789" = { + 0x6c40df5f0b497347, // ECMA-182, + 0x995dc9bbdf1939fa, // XZ + 0x46a5a9388a5beffe, // ISO 3306 + 0xb90956c775a41001, // ISO 3306, input and output inverted + }, + "This is a test of the emergency broadcast system." = { + 0x344fe1d09c983d13, // ECMA-182 + 0x27db187fc15bbc72, // XZ + 0x187184d744afc49e, // ISO 3306 + 0xe7fcf1006b503b61, // ISO 3306, input and output inverted + }, + } + defer delete(vectors) - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) + for vector, expected in vectors { + b := transmute([]u8)vector + ecma := hash.crc64_ecma_182(b) + xz := hash.crc64_xz(b) + iso := hash.crc64_iso_3306(b) + iso2 := hash.crc64_iso_3306_inverse(b) + + testing.expectf(t, ecma == expected[0], "[ CRC-64 ECMA ] Expected: %016x, got: %016x", expected[0], ecma) + testing.expectf(t, xz == expected[1], "[ CRC-64 XZ ] Expected: %016x, got: %016x", expected[1], xz) + testing.expectf(t, iso == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso) + testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2) } } -/* - Benchmarks -*/ +@(test) +test_benchmark_xxh32 :: proc(t: ^testing.T) { + name := "XXH32 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x85f6413c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh32_1MB :: proc(t: ^testing.T) { + name := "XXH32 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x9430f97f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh64 :: proc(t: ^testing.T) { + name := "XXH64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x17bb1103c92c502f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh64_1MB :: proc(t: ^testing.T) { + name := "XXH64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x87d2a1b6e1163ef1) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_64 :: proc(t: ^testing.T) { + name := "XXH3_64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_64_1MB :: proc(t: ^testing.T) { + name := "XXH3_64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_128 :: proc(t: ^testing.T) { + name := "XXH3_128 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_128_1MB :: proc(t: ^testing.T) { + name := "XXH3_128 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0xb6ef17a3448492b6918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +// Benchmarks setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { assert(options != nil) @@ -113,289 +380,14 @@ benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := contex return nil } -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf("\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", +benchmark_print :: proc(name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + log.infof("\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", name, options.rounds, options.processed, time.duration_nanoseconds(options.duration), options.rounds_per_second, options.megabytes_per_second, + location=loc, ) -} - -@test -test_benchmark_runner :: proc(t: ^testing.T) { - fmt.println("Starting benchmarks:") - - name := "XXH32 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x85f6413c, name) - benchmark_print(name, options) - - name = "XXH32 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x9430f97f, name) - benchmark_print(name, options) - - name = "XXH64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x17bb1103c92c502f, name) - benchmark_print(name, options) - - name = "XXH64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x87d2a1b6e1163ef1, name) - benchmark_print(name, options) - - name = "XXH3_64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x918780b90550bf34, name) - benchmark_print(name, options) - - name = "XXH3_128 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_128 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x6ba30a4e9dffe1ff801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_128 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0xb6ef17a3448492b6918780b90550bf34, name) - benchmark_print(name, options) -} - -@test -test_xxhash_large :: proc(t: ^testing.T) { - many_zeroes := make([]u8, 16 * 1024 * 1024) - defer delete(many_zeroes) - - // All at once. - for i, v in ZERO_VECTORS { - b := many_zeroes[:i] - - fmt.printf("[test_xxhash_large] All at once. Size: %v\n", i) - - xxh32 := xxhash.XXH32(b) - xxh64 := xxhash.XXH64(b) - xxh3_64 := xxhash.XXH3_64(b) - xxh3_128 := xxhash.XXH3_128(b) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - } - - when #config(RAND_STATE, -1) >= 0 && #config(RAND_INC, -1) >= 0 { - random_seed := rand.Rand{ - state = u64(#config(RAND_STATE, -1)), - inc = u64(#config(RAND_INC, -1)), - } - fmt.printf("Using user-selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } else { - random_seed := rand.create(u64(intrinsics.read_cycle_counter())) - fmt.printf("Randonly selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } - - // Streamed - for i, v in ZERO_VECTORS { - b := many_zeroes[:i] - - fmt.printf("[test_xxhash_large] Streamed. Size: %v\n", i) - - // bytes_per_update := []int{1, 42, 13, 7, 16, 5, 23, 74, 1024, 511, 1023, 47} - // update_size_idx: int - - xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() - defer xxhash.XXH32_destroy_state(xxh_32_state) - expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state.") - - xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() - defer xxhash.XXH64_destroy_state(xxh_64_state) - expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state.") - - xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() - defer xxhash.XXH3_destroy_state(xxh3_64_state) - expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state.") - - xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() - defer xxhash.XXH3_destroy_state(xxh3_128_state) - expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state.") - - // XXH3_128_update - - for len(b) > 0 { - update_size := min(len(b), rand.int_max(8192, &random_seed)) - if update_size > 4096 { - update_size %= 73 - } - xxhash.XXH32_update (xxh_32_state, b[:update_size]) - xxhash.XXH64_update (xxh_64_state, b[:update_size]) - - xxhash.XXH3_64_update (xxh3_64_state, b[:update_size]) - xxhash.XXH3_128_update(xxh3_128_state, b[:update_size]) - - b = b[update_size:] - } - - // Now finalize - xxh32 := xxhash.XXH32_digest(xxh_32_state) - xxh64 := xxhash.XXH64_digest(xxh_64_state) - - xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) - xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - } -} - -@test -test_xxhash_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying against XXHASH_TEST_VECTOR_SEEDED:") - - buf := make([]u8, 256) - defer delete(buf) - - for seed, table in XXHASH_TEST_VECTOR_SEEDED { - fmt.printf("\tSeed: %v\n", seed) - - for v, i in table { - b := buf[:i] - - xxh32 := xxhash.XXH32(b, u32(seed)) - xxh64 := xxhash.XXH64(b, seed) - xxh3_64 := xxhash.XXH3_64(b, seed) - xxh3_128 := xxhash.XXH3_128(b, seed) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - - if len(b) > xxhash.XXH3_MIDSIZE_MAX { - fmt.printf("XXH3 - size: %v\n", len(b)) - - xxh3_state, _ := xxhash.XXH3_create_state() - xxhash.XXH3_64_reset_with_seed(xxh3_state, seed) - xxhash.XXH3_64_update(xxh3_state, b) - xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state) - xxhash.XXH3_destroy_state(xxh3_state) - xxh3_64s_error := fmt.tprintf("[XXH3_64s(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64_streamed) - expect(t, xxh3_64_streamed == v.xxh3_64, xxh3_64s_error) - - xxh3_state2, _ := xxhash.XXH3_create_state() - xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed) - xxhash.XXH3_128_update(xxh3_state2, b) - xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2) - xxhash.XXH3_destroy_state(xxh3_state2) - xxh3_128s_error := fmt.tprintf("[XXH3_128s(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128_streamed) - expect(t, xxh3_128_streamed == v.xxh3_128, xxh3_128s_error) - } - } - } - - fmt.println("Verifying against XXHASH_TEST_VECTOR_SECRET:") - for secret, table in XXHASH_TEST_VECTOR_SECRET { - fmt.printf("\tSecret:\n\t\t\"%v\"\n", secret) - - secret_bytes := transmute([]u8)secret - - for v, i in table { - b := buf[:i] - - xxh3_128 := xxhash.XXH3_128(b, secret_bytes) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d)] Expected: %32x. Got: %32x.", i, v.xxh3_128_secret, xxh3_128) - - expect(t, xxh3_128 == v.xxh3_128_secret, xxh3_128_error) - } - } -} - -@test -test_crc64_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying CRC-64:") - - vectors := map[string][4]u64 { - "123456789" = { - 0x6c40df5f0b497347, // ECMA-182, - 0x995dc9bbdf1939fa, // XZ - 0x46a5a9388a5beffe, // ISO 3306 - 0xb90956c775a41001, // ISO 3306, input and output inverted - }, - "This is a test of the emergency broadcast system." = { - 0x344fe1d09c983d13, // ECMA-182 - 0x27db187fc15bbc72, // XZ - 0x187184d744afc49e, // ISO 3306 - 0xe7fcf1006b503b61, // ISO 3306, input and output inverted - }, - } - - for vector, expected in vectors { - fmt.println("\tVector:", vector) - b := transmute([]u8)vector - ecma := hash.crc64_ecma_182(b) - xz := hash.crc64_xz(b) - iso := hash.crc64_iso_3306(b) - iso2 := hash.crc64_iso_3306_inverse(b) - - ecma_error := fmt.tprintf("[ CRC-64 ECMA ] Expected: %016x. Got: %016x.", expected[0], ecma) - xz_error := fmt.tprintf("[ CRC-64 XZ ] Expected: %016x. Got: %016x.", expected[1], xz) - iso_error := fmt.tprintf("[ CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[2], iso) - iso2_error := fmt.tprintf("[~CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[3], iso2) - - expect(t, ecma == expected[0], ecma_error) - expect(t, xz == expected[1], xz_error) - expect(t, iso == expected[2], iso_error) - expect(t, iso2 == expected[3], iso2_error) - } } \ No newline at end of file diff --git a/tests/core/hash/test_vectors_xxhash.odin b/tests/core/hash/test_vectors_xxhash.odin index 6a37aef30..f72e2699a 100644 --- a/tests/core/hash/test_vectors_xxhash.odin +++ b/tests/core/hash/test_vectors_xxhash.odin @@ -1,6 +1,4 @@ -/* - Hash Test Vectors -*/ +// Hash Test Vectors package test_core_hash XXHASH_Test_Vectors :: struct #packed { @@ -6789,4 +6787,4 @@ XXHASH_TEST_VECTOR_SECRET := map[string][257]XXHASH_Test_Vectors_With_Secret{ /* XXH3_128_with_secret */ 0x0f9b41191242ade48bbde48dff0d38ec, }, }, -} +} \ No newline at end of file From d7bfbe05523ec4a786e57a916d32b6e849de1681 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 18:38:35 +0200 Subject: [PATCH 058/151] Port `testing\core\text\i18n` --- core/text/i18n/qt_linguist.odin | 2 - tests/core/Makefile | 6 +- tests/core/build.bat | 10 +- tests/core/text/i18n/test_core_text_i18n.odin | 169 +++++++----------- 4 files changed, 75 insertions(+), 112 deletions(-) diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 0e75df873..bdd3f5fd7 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -162,8 +162,6 @@ parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTION context.allocator = allocator data, data_ok := os.read_entire_file(filename) - defer delete(data) - if !data_ok { return {}, .File_Error } return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) diff --git a/tests/core/Makefile b/tests/core/Makefile index 873bd24af..6a01653f0 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -62,12 +62,12 @@ fmt_test: hash_test: $(ODIN) test hash $(COMMON) -o:speed -out:test_hash -i18n_test: - $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n - image_test: $(ODIN) test image $(COMMON) -out:test_core_image +i18n_test: + $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n + linalg_glsl_math_test: $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math diff --git a/tests/core/build.bat b/tests/core/build.bat index 4748c3071..c06a9269f 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -54,6 +54,11 @@ echo Running core:image tests echo --- %PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b +echo --- +echo Running core:text/i18n tests +echo --- +%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b + echo --- echo Running core:math tests echo --- @@ -99,11 +104,6 @@ echo Running core:strings tests echo --- %PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b -echo --- -echo Running core:text/i18n tests -echo --- -%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b - echo --- echo Running core:thread tests echo --- diff --git a/tests/core/text/i18n/test_core_text_i18n.odin b/tests/core/text/i18n/test_core_text_i18n.odin index dcbdeb0c4..f6cffc318 100644 --- a/tests/core/text/i18n/test_core_text_i18n.odin +++ b/tests/core/text/i18n/test_core_text_i18n.odin @@ -1,31 +1,9 @@ package test_core_text_i18n -import "core:mem" -import "core:fmt" -import "core:os" +import "base:runtime" import "core:testing" import "core:text/i18n" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} T :: i18n.get Test :: struct { @@ -37,25 +15,28 @@ Test :: struct { Test_Suite :: struct { file: string, - loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error), + loader: proc(string, i18n.Parse_Options, proc(int) -> int, runtime.Allocator) -> (^i18n.Translation, i18n.Error), plural: proc(int) -> int, err: i18n.Error, options: i18n.Parse_Options, tests: []Test, } -// Custom pluralizer for plur.mo -plur_mo_pluralizer :: proc(n: int) -> (slot: int) { - switch { - case n == 1: return 0 - case n != 0 && n % 1_000_000 == 0: return 1 - case: return 2 - } -} +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/I18N/" -TESTS := []Test_Suite{ - { - file = "assets/I18N/plur.mo", +@(test) +test_custom_pluralizer :: proc(t: ^testing.T) { + // Custom pluralizer for plur.mo + plur_mo_pluralizer :: proc(n: int) -> (slot: int) { + switch { + case n == 1: return 0 + case n != 0 && n % 1_000_000 == 0: return 1 + case: return 2 + } + } + + test(t, { + file = TEST_SUITE_PATH + "plur.mo", loader = i18n.parse_mo_file, plural = plur_mo_pluralizer, tests = { @@ -66,14 +47,16 @@ TESTS := []Test_Suite{ {"", "Message1/plural", "This is message 1", 1}, {"", "Message1/plural", "This is message 1 - plural A", 1_000_000}, {"", "Message1/plural", "This is message 1 - plural B", 42}, - // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - { - file = "assets/I18N/mixed_context.mo", +@(test) +test_mixed_context :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", loader = i18n.parse_mo_file, plural = nil, tests = { @@ -84,19 +67,25 @@ TESTS := []Test_Suite{ // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - { - file = "assets/I18N/mixed_context.mo", +@(test) +test_mixed_context_dupe :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", loader = i18n.parse_mo_file, plural = nil, // Message1 exists twice, once within Context, which has been merged into "" err = .Duplicate_Key, options = {merge_sections = true}, - }, + }) +} - { - file = "assets/I18N/nl_NL.mo", +@(test) +test_nl_mo :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL.mo", loader = i18n.parse_mo_file, plural = nil, // Default pluralizer tests = { @@ -111,12 +100,13 @@ TESTS := []Test_Suite{ // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - - // QT Linguist with default loader options. - { - file = "assets/I18N/nl_NL-qt-ts.ts", +@(test) +test_qt_linguist :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer tests = { @@ -131,11 +121,13 @@ TESTS := []Test_Suite{ {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, {"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - // QT Linguist, merging sections. - { - file = "assets/I18N/nl_NL-qt-ts.ts", +@(test) +test_qt_linguist_merge_sections :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer options = {merge_sections = true}, @@ -154,65 +146,38 @@ TESTS := []Test_Suite{ {"apple_count", "%d apple(s)", "%d apple(s)", 1}, {"apple_count", "%d apple(s)", "%d apple(s)", 42}, }, - }, + }) +} - // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key_err :: proc(t: ^testing.T) { + test(t, { // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer options = {merge_sections = true}, err = .Duplicate_Key, - }, + }) +} - // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key :: proc(t: ^testing.T) { + test(t, { // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer - }, + }) } -@test -tests :: proc(t: ^testing.T) { - cat: ^i18n.Translation - err: i18n.Error +test :: proc(t: ^testing.T, suite: Test_Suite, loc := #caller_location) { + cat, err := suite.loader(suite.file, suite.options, suite.plural, context.allocator) + testing.expectf(t, err == suite.err, "Expected loading %v to return %v, got %v", suite.file, suite.err, err, loc=loc) - for suite in TESTS { - cat, err = suite.loader(suite.file, suite.options, suite.plural, context.allocator) - - msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err) - expect(t, err == suite.err, msg) - - if err == .None { - for test in suite.tests { - val := T(test.section, test.key, test.n, cat) - - msg = fmt.tprintf("Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val) - expect(t, val == test.val, msg) - } - } - i18n.destroy(cat) - } -} - -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - t := testing.T{} - tests(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } - - if len(track.allocation_map) > 0 { - fmt.println() - for _, v in track.allocation_map { - fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) + if err == .None { + for test in suite.tests { + val := T(test.section, test.key, test.n, cat) + testing.expectf(t, val == test.val, "Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val, loc=loc) } } + i18n.destroy(cat) } \ No newline at end of file From b0faab29e0c4cfcde54b5ffa66f5b43c77283d3d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 19:42:44 +0200 Subject: [PATCH 059/151] Port `tests\core\math`, `math\linalg\glsl` and `math\noise` --- tests/core/Makefile | 14 +- tests/core/build.bat | 6 +- .../linalg/glsl/test_linalg_glsl_math.odin | 20 +- .../core/math/noise/test_core_math_noise.odin | 195 +++++-------- tests/core/math/test_core_math.odin | 258 +++++++----------- 5 files changed, 180 insertions(+), 313 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 6a01653f0..eb16f6645 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -68,21 +68,21 @@ image_test: i18n_test: $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n +math_test: + $(ODIN) test math $(COMMON) -out:test_core_math + linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math + $(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math + +noise_test: + $(ODIN) test math/noise $(COMMON) -out:test_noise match_test: $(ODIN) run text/match $(COMMON) -out:test_core_match -math_test: - $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math - net_test: $(ODIN) run net $(COMMON) -out:test_core_net -noise_test: - $(ODIN) run math/noise $(COMMON) -out:test_noise - os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index c06a9269f..f14579056 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -62,17 +62,17 @@ echo --- echo --- echo Running core:math tests echo --- -%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b +%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b echo --- echo Running core:math/linalg/glsl tests echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b +%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b +%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b echo --- echo Running core:net diff --git a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin index cf91b8a97..6d4571b24 100644 --- a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin +++ b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin @@ -1,24 +1,10 @@ // Tests "linalg_glsl_math.odin" in "core:math/linalg/glsl". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/linalg/glsl/test_linalg_glsl_math.odin -collection:tests=./tests package test_core_math_linalg_glsl_math import glsl "core:math/linalg/glsl" -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - - t := testing.T{} - - test_fract_f32(&t) - test_fract_f64(&t) - - tc.report(&t) -} @test test_fract_f32 :: proc(t: ^testing.T) { @@ -45,7 +31,7 @@ test_fract_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } } @@ -74,6 +60,6 @@ test_fract_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } -} +} \ No newline at end of file diff --git a/tests/core/math/noise/test_core_math_noise.odin b/tests/core/math/noise/test_core_math_noise.odin index a0360e695..f835cf58c 100644 --- a/tests/core/math/noise/test_core_math_noise.odin +++ b/tests/core/math/noise/test_core_math_noise.odin @@ -2,42 +2,6 @@ package test_core_math_noise import "core:testing" import "core:math/noise" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -V2 :: noise.Vec2 -V3 :: noise.Vec3 -V4 :: noise.Vec4 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - noise_test(&t) - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} Test_Vector :: struct { seed: i64, @@ -51,6 +15,10 @@ Test_Vector :: struct { }, } +V2 :: noise.Vec2 +V3 :: noise.Vec3 +V4 :: noise.Vec4 + SEED_1 :: 2324223232 SEED_2 :: 932466901 SEED_3 :: 9321 @@ -59,93 +27,78 @@ COORD_1 :: V4{ 242.0, 3433.0, 920.0, 222312.0} COORD_2 :: V4{ 590.0, 9411.0, 5201.0, 942124256.0} COORD_3 :: V4{12090.0, 19411.0, 81950901.0, 4224219.0} -Noise_Tests := []Test_Vector{ - /* - `noise_2d` tests. - */ - {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}, - {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}, - {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}, - - /* - `noise_2d_improve_x` tests. - */ - {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}, - {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}, - {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}, - - /* - `noise_3d_improve_xy` tests. - */ - {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}, - {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}, - {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}, - - /* - `noise_3d_improve_xz` tests. - */ - {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}, - {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}, - {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_4d_improve_xyz_improve_xy` tests. - */ - {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}, - - /* - `noise_4d_improve_xyz_improve_xz` tests. - */ - {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}, - - /* - `noise_4d_improve_xyz` tests. - */ - {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}, - {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}, - {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}, - - /* - `noise_4d_fallback` tests. - */ - {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}, - {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}, - {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}, - +@(test) +test_noise_2d :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}) + test(t, {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}) + test(t, {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}) } -noise_test :: proc(t: ^testing.T) { - for test in Noise_Tests { - output: f32 +@(test) +test_noise_2d_improve_x :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}) + test(t, {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}) + test(t, {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}) +} - switch coord in test.coord { - case V2: - output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) - case V3: - output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) - case V4: - output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) - } - - error := fmt.tprintf("Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) - expect(t, test.expected == output, error) +@(test) +test_noise_3d_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}) + test(t, {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}) + test(t, {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}) +} + +@(test) +test_noise_3d_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}) + test(t, {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}) + test(t, {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}) +} + +@(test) +test_noise_3d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}) + test(t, {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}) + test(t, {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}) +} + +@(test) +test_noise_4d_improve_xyz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}) + test(t, {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}) + test(t, {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}) +} + +@(test) +test_noise_4d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}) + test(t, {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}) + test(t, {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}) +} + +test :: proc(t: ^testing.T, test: Test_Vector) { + output: f32 + switch coord in test.coord { + case V2: + output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) + case V3: + output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) + case V4: + output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) } + testing.expectf(t, test.expected == output, "Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) } \ No newline at end of file diff --git a/tests/core/math/test_core_math.odin b/tests/core/math/test_core_math.odin index df989bff6..2a752e366 100644 --- a/tests/core/math/test_core_math.odin +++ b/tests/core/math/test_core_math.odin @@ -1,49 +1,8 @@ // Tests "math.odin" in "core:math". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/test_core_math.odin -collection:tests=./tests package test_core_math -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_classify_f16(&t) - test_classify_f32(&t) - test_classify_f64(&t) - - test_trunc_f16(&t) - test_trunc_f32(&t) - test_trunc_f64(&t) - - test_round_f16(&t) - test_round_f32(&t) - test_round_f64(&t) - - test_nan(&t) - test_acos(&t) - test_acosh(&t) - test_asin(&t) - test_asinh(&t) - test_atan(&t) - test_atanh(&t) - test_atan2(&t) - test_cos(&t) - test_cosh(&t) - test_sin(&t) - test_sinh(&t) - test_sqrt(&t) - test_tan(&t) - test_tanh(&t) - test_large_cos(&t) - test_large_sin(&t) - test_large_tan(&t) - - tc.report(&t) -} @test test_classify_f16 :: proc(t: ^testing.T) { @@ -68,7 +27,7 @@ test_classify_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } /* Check all subnormals (exponent 0, 10-bit significand non-zero) */ @@ -76,7 +35,7 @@ test_classify_f16 :: proc(t: ^testing.T) { v := transmute(f16)i r = math.classify_f16(v) e :: math.Float_Class.Subnormal - tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e)) + testing.expectf(t, r == e, "%h -> %v != %v", v, r, e) } } @@ -103,7 +62,7 @@ test_classify_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -130,7 +89,7 @@ test_classify_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -175,16 +134,16 @@ test_trunc_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) v = math.QNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) } @test @@ -237,16 +196,16 @@ test_trunc_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -299,16 +258,16 @@ test_trunc_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @test @@ -352,16 +311,16 @@ test_round_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) v = math.QNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) } @test @@ -414,16 +373,16 @@ test_round_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", i, d.v, r, d.e) } v = math.SNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -476,16 +435,16 @@ test_round_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @@ -1033,17 +992,17 @@ tolerance :: proc(a, b, e: f64) -> bool { } close :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 1e-9) - // tc.expect(t, ok, fmt.tprintf("%.15g is not close to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not close to %.15g", a, b, loc=loc) return ok } veryclose :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 4e-14) - // tc.expect(t, ok, fmt.tprintf("%.15g is not veryclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not veryclose to %.15g", a, b, loc=loc) return ok } soclose :: proc(t: ^testing.T, a, b, e: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, e) - // tc.expect(t, ok, fmt.tprintf("%.15g is not soclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not soclose to %.15g", a, b, loc=loc) return ok } alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { @@ -1054,34 +1013,34 @@ alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { case a == b: ok = math.signbit(a) == math.signbit(b) } - // tc.expect(t, ok, fmt.tprintf("%.15g is not alike to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not alike to %.15g", a, b, loc=loc) return ok } @test -test_nan :: proc(t: ^testing.T) { +test_nan32 :: proc(t: ^testing.T) { + float32 := f32(NaN) + equal := float32 == float32 + testing.expectf(t, !equal, "float32(NaN) is %.15g, expected NaN", float32) +} + +@test +test_nan64 :: proc(t: ^testing.T) { float64 := NaN - if float64 == float64 { - tc.errorf(t, "NaN returns %.15g, expected NaN", float64) - } - float32 := f32(float64) - if float32 == float32 { - tc.errorf(t, "float32(NaN) is %.15g, expected NaN", float32) - } + equal := float64 == float64 + testing.expectf(t, !equal, "NaN returns %.15g, expected NaN", float64) } @test test_acos :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.acos(a); !close(t, acos[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) - } + f := math.acos(a) + testing.expectf(t, close(t, acos[i], f), "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) } for _, i in vfacos_sc { - if f := math.acos(vfacos_sc[i]); !alike(t, acos_sc[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) - } + f := math.acos(vfacos_sc[i]) + testing.expectf(t, alike(t, acos_sc[i], f), "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) } } @@ -1089,14 +1048,12 @@ test_acos :: proc(t: ^testing.T) { test_acosh :: proc(t: ^testing.T) { for _, i in vf { a := 1 + abs(vf[i]) - if f := math.acosh(a); !veryclose(t, acosh[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) - } + f := math.acosh(a) + testing.expectf(t, veryclose(t, acosh[i], f), "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) } for _, i in vfacosh_sc { - if f := math.acosh(vfacosh_sc[i]); !alike(t, acosh_sc[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) - } + f := math.acosh(vfacosh_sc[i]) + testing.expectf(t, alike(t, acosh_sc[i], f), "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) } } @@ -1104,42 +1061,36 @@ test_acosh :: proc(t: ^testing.T) { test_asin :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.asin(a); !veryclose(t, asin[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) - } + f := math.asin(a) + testing.expectf(t, veryclose(t, asin[i], f), "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) } for _, i in vfasin_sc { - if f := math.asin(vfasin_sc[i]); !alike(t, asin_sc[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) - } + f := math.asin(vfasin_sc[i]) + testing.expectf(t, alike(t, asin_sc[i], f), "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) } } @test test_asinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.asinh(vf[i]); !veryclose(t, asinh[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) - } + f := math.asinh(vf[i]) + testing.expectf(t, veryclose(t, asinh[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) } for _, i in vfasinh_sc { - if f := math.asinh(vfasinh_sc[i]); !alike(t, asinh_sc[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) - } + f := math.asinh(vfasinh_sc[i]) + testing.expectf(t, alike(t, asinh_sc[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) } } @test test_atan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan(vf[i]); !veryclose(t, atan[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) - } + f := math.atan(vf[i]) + testing.expectf(t, veryclose(t, atan[i], f), "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) } for _, i in vfatan_sc { - if f := math.atan(vfatan_sc[i]); !alike(t, atan_sc[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) - } + f := math.atan(vfatan_sc[i]) + testing.expectf(t, alike(t, atan_sc[i], f), "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) } } @@ -1147,84 +1098,72 @@ test_atan :: proc(t: ^testing.T) { test_atanh :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.atanh(a); !veryclose(t, atanh[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) - } + f := math.atanh(a) + testing.expectf(t, veryclose(t, atanh[i], f), "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) } for _, i in vfatanh_sc { - if f := math.atanh(vfatanh_sc[i]); !alike(t, atanh_sc[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) - } + f := math.atanh(vfatanh_sc[i]) + testing.expectf(t, alike(t, atanh_sc[i], f), "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) } } @test test_atan2 :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan2(10, vf[i]); !veryclose(t, atan2[i], f) { - tc.errorf(t, "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) - } + f := math.atan2(10, vf[i]) + testing.expectf(t, veryclose(t, atan2[i], f), "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) } for _, i in vfatan2_sc { - if f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]); !alike(t, atan2_sc[i], f) { - tc.errorf(t, "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) - } + f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]) + testing.expectf(t, alike(t, atan2_sc[i], f), "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) } } @test test_cos :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cos(vf[i]); !veryclose(t, cos[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) - } + f := math.cos(vf[i]) + testing.expectf(t, veryclose(t, cos[i], f), "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) } for _, i in vfcos_sc { - if f := math.cos(vfcos_sc[i]); !alike(t, cos_sc[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) - } + f := math.cos(vfcos_sc[i]) + testing.expectf(t, alike(t, cos_sc[i], f), "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) } } @test test_cosh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cosh(vf[i]); !close(t, cosh[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) - } + f := math.cosh(vf[i]) + testing.expectf(t, close(t, cosh[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) } for _, i in vfcosh_sc { - if f := math.cosh(vfcosh_sc[i]); !alike(t, cosh_sc[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) - } + f := math.cosh(vfcosh_sc[i]) + testing.expectf(t, alike(t, cosh_sc[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) } } @test test_sin :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sin(vf[i]); !veryclose(t, sin[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) - } + f := math.sin(vf[i]) + testing.expectf(t, veryclose(t, sin[i], f), "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) } for _, i in vfsin_sc { - if f := math.sin(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.sin(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_sinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sinh(vf[i]); !close(t, sinh[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) - } + f := math.sinh(vf[i]) + testing.expectf(t, close(t, sinh[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) } for _, i in vfsinh_sc { - if f := math.sinh(vfsinh_sc[i]); !alike(t, sinh_sc[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) - } + f := math.sinh(vfsinh_sc[i]) + testing.expectf(t, alike(t, sinh_sc[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) } } @@ -1232,38 +1171,33 @@ test_sinh :: proc(t: ^testing.T) { test_sqrt :: proc(t: ^testing.T) { for _, i in vf { a := abs(vf[i]) - if f := math.sqrt(a); !veryclose(t, sqrt[i], f) { - tc.errorf(t, "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) - } + f := math.sqrt(a) + testing.expectf(t, veryclose(t, sqrt[i], f), "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) } } @test test_tan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tan(vf[i]); !veryclose(t, tan[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) - } + f := math.tan(vf[i]) + testing.expectf(t, veryclose(t, tan[i], f), "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) } // same special cases as Sin for _, i in vfsin_sc { - if f := math.tan(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.tan(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_tanh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tanh(vf[i]); !veryclose(t, tanh[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) - } + f := math.tanh(vf[i]) + testing.expectf(t, veryclose(t, tanh[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) } for _, i in vftanh_sc { - if f := math.tanh(vftanh_sc[i]); !alike(t, tanh_sc[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) - } + f := math.tanh(vftanh_sc[i]) + testing.expectf(t, alike(t, tanh_sc[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) } } @@ -1273,9 +1207,7 @@ test_large_cos :: proc(t: ^testing.T) { for _, i in vf { f1 := cosLarge[i] f2 := math.cos(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1285,9 +1217,7 @@ test_large_sin :: proc(t: ^testing.T) { for _, i in vf { f1 := sinLarge[i] f2 := math.sin(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1297,8 +1227,6 @@ test_large_tan :: proc(t: ^testing.T) { for _, i in vf { f1 := tanLarge[i] f2 := math.tan(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } \ No newline at end of file From 8383a45b62df9a3f28df0e0967bdd2b864c90cb9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 23:25:34 +0200 Subject: [PATCH 060/151] Port `tests\core\text\match` --- tests/core/Makefile | 6 +- tests/core/build.bat | 5 + .../core/text/match/test_core_text_match.odin | 161 +++++++----------- 3 files changed, 71 insertions(+), 101 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index eb16f6645..ad8209b86 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -68,6 +68,9 @@ image_test: i18n_test: $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n +match_test: + $(ODIN) test text/match $(COMMON) -out:test_core_match + math_test: $(ODIN) test math $(COMMON) -out:test_core_math @@ -77,9 +80,6 @@ linalg_glsl_math_test: noise_test: $(ODIN) test math/noise $(COMMON) -out:test_noise -match_test: - $(ODIN) run text/match $(COMMON) -out:test_core_match - net_test: $(ODIN) run net $(COMMON) -out:test_core_net diff --git a/tests/core/build.bat b/tests/core/build.bat index f14579056..f77c4adfc 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -59,6 +59,11 @@ echo Running core:text/i18n tests echo --- %PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b +echo --- +echo Running text:match tests +echo --- +%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b + echo --- echo Running core:math tests echo --- diff --git a/tests/core/text/match/test_core_text_match.odin b/tests/core/text/match/test_core_text_match.odin index eadd17433..5716b06fb 100644 --- a/tests/core/text/match/test_core_text_match.odin +++ b/tests/core/text/match/test_core_text_match.odin @@ -2,31 +2,6 @@ package test_strlib import "core:text/match" import "core:testing" -import "core:fmt" -import "core:os" -import "core:io" - -TEST_count: int -TEST_fail: int - -// inline expect with custom props -failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { - TEST_count += 1 - - if !ok { - fmt.printf(/*t.w,*/ "%v: ", loc) - t.error_count += 1 - TEST_fail += 1 - } - - return !ok -} - -expect :: testing.expect - -logf :: proc(t: ^testing.T, format: string, args: ..any) { - fmt.printf(/*t.w,*/ format, ..args) -} // find correct byte offsets @test @@ -61,18 +36,17 @@ test_find :: proc(t: ^testing.T) { { "helelo", "h.-l", 0, { 0, 3, true } }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p, entry.offset) start, end, ok := match.matcher_find(&matcher) success := entry.match.ok == ok && start == entry.match.start && end == entry.match.end - if failed(t, success) { - logf(t, "Find %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tSTART: %d == %d?\n", entry.match.start, start) - logf(t, "\tEND: %d == %d?\n", entry.match.end, end) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q, START: %d == %d? END: %d == %d? Err: %v Length %d", + entry.s, entry.p, entry.match.start, start, entry.match.end, end, matcher.err, matcher.captures_length, + ) } } @@ -178,17 +152,17 @@ test_match :: proc(t: ^testing.T) { { "testing _this_ out", "%b_", "", false }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p) result, ok := match.matcher_match(&matcher) success := entry.ok == ok && result == entry.result - if failed(t, success) { - logf(t, "Match %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tResults: WANTED %s\tGOT %s\n", entry.result, result) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q WANTED %q GOT %q Err: %v Length %d", + entry.s, entry.p, entry.result, result, matcher.err, matcher.captures_length, + ) } } @@ -203,19 +177,23 @@ test_captures :: proc(t: ^testing.T) { compare_captures :: proc(t: ^testing.T, test: ^Temp, haystack: string, comp: []string, loc := #caller_location) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := len(comp) == length && err == .OK - if failed(t, result == true) { - logf(t, "Captures Compare Failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLengths: %v != %v\n", len(comp), length) - } + testing.expectf( + t, + result, + "Captures Compare Failed! Lengths: %v != %v Err: %v", + len(comp), length, err, + ) for i in 0.. %s != %s\n", comp[i], text) - } + testing.expectf( + t, + comp[i] == text, + "Capture don't equal -> %q != %q\n", + comp[i], text, + ) } } @@ -224,11 +202,12 @@ test_captures :: proc(t: ^testing.T) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := length > 0 && err == .OK - if failed(t, result == ok) { - logf(t, "Capture match failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLength: %v\n", length) - } + testing.expectf( + t, + result == ok, + "Capture match failed! Length: %v Pattern: %q Haystack: %q Err: %v", + length, test.pattern, haystack, err, + ) } temp := Temp { pattern = "(one).+" } @@ -253,15 +232,8 @@ test_captures :: proc(t: ^testing.T) { cap2 := captures[2] text1 := haystack[cap1.byte_start:cap1.byte_end] text2 := haystack[cap2.byte_start:cap2.byte_end] - expect(t, text1 == "233", "Multi-Capture failed at 1") - expect(t, text2 == "hello", "Multi-Capture failed at 2") - } -} - -gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { - if failed(t, a[index] == b) { - logf(t, "GMATCH %d failed!\n", index) - logf(t, "\t%s != %s\n", a[index], b) + testing.expect(t, text1 == "233", "Multi-Capture failed at 1") + testing.expect(t, text2 == "hello", "Multi-Capture failed at 2") } } @@ -298,9 +270,9 @@ test_gmatch :: proc(t: ^testing.T) { @test test_gsub :: proc(t: ^testing.T) { result := match.gsub("testing123testing", "%d+", " sup ", context.temp_allocator) - expect(t, result == "testing sup testing", "GSUB 0: failed") + testing.expect(t, result == "testing sup testing", "GSUB 0: failed") result = match.gsub("testing123testing", "%a+", "345", context.temp_allocator) - expect(t, result == "345123345", "GSUB 1: failed") + testing.expect(t, result == "345123345", "GSUB 1: failed") } @test @@ -313,10 +285,12 @@ test_gfind :: proc(t: ^testing.T) { index: int for word in match.gfind(s, pattern, &captures) { - if failed(t, output[index] == word) { - logf(t, "GFIND %d failed!\n", index) - logf(t, "\t%s != %s\n", output[index], word) - } + testing.expectf( + t, + output[index] == word, + "GFIND %d failed! %q != %q", + index, output[index], word, + ) index += 1 } } @@ -332,11 +306,12 @@ test_frontier :: proc(t: ^testing.T) { call :: proc(data: rawptr, word: string, haystack: string, captures: []match.Match) { temp := cast(^Temp) data - if failed(temp.t, word == temp.output[temp.index]) { - logf(temp.t, "GSUB_WITH %d failed!\n", temp.index) - logf(temp.t, "\t%s != %s\n", temp.output[temp.index], word) - } - + testing.expectf( + temp.t, + word == temp.output[temp.index], + "GSUB_WITH %d failed! %q != %q", + temp.index, temp.output[temp.index], word, + ) temp.index += 1 } @@ -369,31 +344,21 @@ test_case_insensitive :: proc(t: ^testing.T) { pattern := match.pattern_case_insensitive("test", 256, context.temp_allocator) goal := "[tT][eE][sS][tT]" - if failed(t, pattern == goal) { - logf(t, "Case Insensitive Pattern doesn't match result\n") - logf(t, "\t%s != %s\n", pattern, goal) - } + testing.expectf( + t, + pattern == goal, + "Case Insensitive Pattern doesn't match result. %q != %q", + pattern, goal, + ) } } -main :: proc() { - t: testing.T - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) - // t.w = w - - test_find(&t) - test_match(&t) - test_captures(&t) - test_gmatch(&t) - test_gsub(&t) - test_gfind(&t) - test_frontier(&t) - test_utf8(&t) - test_case_insensitive(&t) - - fmt.wprintf(w, "%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +@(private) +gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { + testing.expectf( + t, + a[index] == b, + "GMATCH %d failed! %q != %q", + index, a[index], b, + ) +} \ No newline at end of file From 9829a02571250a27b81e5560e5c4add4df3b3f62 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:12:18 +0200 Subject: [PATCH 061/151] Port `tests\core\odin` --- tests/core/Makefile | 11 +++++-- tests/core/build.bat | 2 +- tests/core/odin/test_parser.odin | 53 +++++++------------------------ tests/core/test_core_odin | Bin 0 -> 1401192 bytes 4 files changed, 22 insertions(+), 44 deletions(-) create mode 100644 tests/core/test_core_odin diff --git a/tests/core/Makefile b/tests/core/Makefile index ad8209b86..2cd7304ac 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -21,6 +21,7 @@ all_bsd: download_test_assets \ match_test \ math_test \ noise_test \ + odin_test \ os_exit_test \ reflect_test \ runtime_test \ @@ -86,8 +87,11 @@ net_test: os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 +odin_test: + $(ODIN) test odin $(COMMON) -out:test_core_odin + reflect_test: - $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect + $(ODIN) test reflect $(COMMON) -out:test_core_reflect runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime @@ -102,4 +106,7 @@ thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread time_test: - $(ODIN) run time $(COMMON) -out:test_core_time \ No newline at end of file + $(ODIN) run time $(COMMON) -out:test_core_time + +clean: + rm test_* \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index f77c4adfc..418908884 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -87,7 +87,7 @@ echo --- echo --- echo Running core:odin tests echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b +%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b echo --- echo Running core:reflect tests diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index 821b7a53c..772ae5982 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -1,58 +1,29 @@ package test_core_odin_parser -import "core:fmt" import "core:odin/ast" import "core:odin/parser" -import "core:os" +import "base:runtime" import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_parse_demo(&t) - test_parse_bitfield(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - - @test test_parse_demo :: proc(t: ^testing.T) { - pkg, ok := parser.parse_package_from_path("examples/demo") + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + pkg, ok := parser.parse_package_from_path(ODIN_ROOT + "examples/demo") - expect(t, ok == true, "parser.parse_package_from_path failed") + testing.expect(t, ok, "parser.parse_package_from_path failed") for key, value in pkg.files { - expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key)) + testing.expectf(t, value.syntax_error_count == 0, "%v should contain zero errors", key) } } @test test_parse_bitfield :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + file := ast.File{ fullpath = "test.odin", src = ` @@ -78,5 +49,5 @@ Foo :: bit_field uint { p := parser.default_parser() ok := parser.parse_file(&p, &file) - expect(t, ok == true, "bad parse") -} + testing.expect(t, ok, "bad parse") +} \ No newline at end of file diff --git a/tests/core/test_core_odin b/tests/core/test_core_odin new file mode 100644 index 0000000000000000000000000000000000000000..bdf316228a8bddc5f4a0998789b00dc1f182e9f4 GIT binary patch literal 1401192 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 B9g6?} literal 0 HcmV?d00001 From 80b115748f319cc4d0d556cb89e30493afd92a30 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:27:30 +0200 Subject: [PATCH 062/151] Port `tests\core\reflect` --- tests/core/Makefile | 2 +- tests/core/build.bat | 4 +- tests/core/reflect/test_core_reflect.odin | 87 +++++++++-------------- 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 2cd7304ac..fa2c25810 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -94,7 +94,7 @@ reflect_test: $(ODIN) test reflect $(COMMON) -out:test_core_reflect runtime_test: - $(ODIN) run runtime $(COMMON) -out:test_core_runtime + $(ODIN) test runtime $(COMMON) -out:test_core_runtime slice_test: $(ODIN) run slice $(COMMON) -out:test_core_slice diff --git a/tests/core/build.bat b/tests/core/build.bat index 418908884..4eca48866 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -92,12 +92,12 @@ echo --- echo --- echo Running core:reflect tests echo --- -%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b +%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b echo --- echo Running core:runtime tests echo --- -%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b +%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b echo --- echo Running core:slice tests diff --git a/tests/core/reflect/test_core_reflect.odin b/tests/core/reflect/test_core_reflect.odin index a3a66f968..7d2394688 100644 --- a/tests/core/reflect/test_core_reflect.odin +++ b/tests/core/reflect/test_core_reflect.odin @@ -1,21 +1,8 @@ // Tests "core:reflect/reflect". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/reflect/test_core_reflect.odin -out=tests/core/test_core_reflect -collection:tests=./tests package test_core_reflect -import "core:fmt" import "core:reflect" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_as_u64(&t) - test_as_f64(&t) - - tc.report(&t) -} @test test_as_u64 :: proc(t: ^testing.T) { @@ -31,9 +18,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -48,9 +34,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -65,9 +50,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -82,9 +66,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -102,9 +85,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -118,8 +100,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v -> %v != %v", d.v, r, d.e) } } { @@ -132,8 +114,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v -> %v != %v", d.v, r, d.e) } } { @@ -146,8 +128,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } } @@ -166,8 +148,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v != %v", d.v, r, d.e) } } { @@ -182,8 +164,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v != %v", d.v, r, d.e) } } { @@ -198,8 +180,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v != %v", d.v, r, d.e) } } { @@ -214,8 +196,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v != %v", d.v, r, d.e) } } { @@ -231,9 +213,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (%H) != %v (%H)", d.v, r, r, d.e, d.e) } } { @@ -247,9 +228,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -262,9 +242,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -277,8 +256,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } -} +} \ No newline at end of file From ed0384c102c97edc6c35f74c7c6c0a8fa15dad9f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:35:57 +0200 Subject: [PATCH 063/151] Port `tests\core\runtime` --- tests/core/runtime/test_core_runtime.odin | 43 +++-------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 786cf003a..008146dcf 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -1,43 +1,10 @@ package test_core_runtime -import "core:fmt" import "base:intrinsics" import "core:mem" -import "core:os" -import "core:reflect" import "base:runtime" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } -} - -main :: proc() { - t := testing.T{} - - test_temp_allocator_big_alloc_and_alignment(&t) - test_temp_allocator_alignment_boundary(&t) - test_temp_allocator_returns_correct_size(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - // Tests that having space for the allocation, but not for the allocation and alignment // is handled correctly. @(test) @@ -47,7 +14,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) { _, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120) _, err := mem.alloc(112, 32) - expect_value(t, err, nil) + testing.expect(t, err == nil) } // Tests that big allocations with big alignments are handled correctly. @@ -58,7 +25,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) { mappy: map[[8]int]int err := reserve(&mappy, 50000) - expect_value(t, err, nil) + testing.expect(t, err == nil) } @(test) @@ -67,6 +34,6 @@ test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) { context.allocator = runtime.arena_allocator(&arena) bytes, err := mem.alloc_bytes(10, 16) - expect_value(t, err, nil) - expect_value(t, len(bytes), 10) -} + testing.expect(t, err == nil) + testing.expect(t, len(bytes) == 10) +} \ No newline at end of file From 9ba02e888ddf5e448eb5f2463070f39d183be052 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:50:58 +0200 Subject: [PATCH 064/151] Port `tests\core\slice` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/slice/test_core_slice.odin | 136 ++++++++------------------ 3 files changed, 41 insertions(+), 99 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index fa2c25810..453b84dbe 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -97,7 +97,7 @@ runtime_test: $(ODIN) test runtime $(COMMON) -out:test_core_runtime slice_test: - $(ODIN) run slice $(COMMON) -out:test_core_slice + $(ODIN) test slice $(COMMON) -out:test_core_slice strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index 4eca48866..9a21d6b79 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -102,7 +102,7 @@ echo --- echo --- echo Running core:slice tests echo --- -%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b +%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b echo --- echo Running core:strings tests diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 06329ddda..4a464503f 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -1,56 +1,16 @@ package test_core_slice import "core:slice" -import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "core:math/rand" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_sort_with_indices(&t) - test_binary_search(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_sort_with_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + r := rand.create(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -61,7 +21,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64(&r) } // Sort @@ -69,7 +29,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.init(&r, t.seed) for v, i in f_idx { r_idx[v] = i @@ -79,14 +39,14 @@ test_sort_with_indices :: proc(t: ^testing.T) { for v, i in vals { if i > 0 { val_pass := v >= last - expect(t, val_pass, "Expected values to have been sorted.") + testing.expect(t, val_pass, "Expected randomized test values to have been sorted") if !val_pass { break } } idx_pass := vals[r_idx[i]] == rand.uint64(&r) - expect(t, idx_pass, "Expected index to have been sorted.") + testing.expect(t, idx_pass, "Expected index to have been sorted") if !idx_pass { break } @@ -97,16 +57,11 @@ test_sort_with_indices :: proc(t: ^testing.T) { @test test_sort_by_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + r := rand.create(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -117,7 +72,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64(&r) } // Sort @@ -125,7 +80,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.init(&r, t.seed) { indices := make([]int, test_size) @@ -138,7 +93,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(sorted_indices) for v, i in sorted_indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -154,7 +109,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices_overwrite(indices, f_idx) for v, i in indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -174,7 +129,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices(indices, swap, f_idx) for v, i in swap { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -185,61 +140,48 @@ test_sort_by_indices :: proc(t: ^testing.T) { @test test_binary_search :: proc(t: ^testing.T) { - builder := strings.Builder{} - defer strings.builder_destroy(&builder) - - test_search :: proc(t: ^testing.T, b: ^strings.Builder, s: []i32, v: i32) -> (int, bool) { - log(t, fmt.sbprintf(b, "Searching for %v in %v", v, s)) - strings.builder_reset(b) - index, found := slice.binary_search(s, v) - log(t, fmt.sbprintf(b, "index: %v, found: %v", index, found)) - strings.builder_reset(b ) - - return index, found - } - index: int found: bool s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} - index, found = test_search(t, &builder, s, 13) - expect(t, index == 9, "Expected index to be 9.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 13) + testing.expect(t, index == 9, "Expected index to be 9") + testing.expect(t, found == true, "Expected found to be true") - index, found = test_search(t, &builder, s, 4) - expect(t, index == 7, "Expected index to be 7.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 4) + testing.expect(t, index == 7, "Expected index to be 7.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 100) - expect(t, index == 13, "Expected index to be 13.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 100) + testing.expect(t, index == 13, "Expected index to be 13.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 1) - expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 1) + testing.expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, s, -1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, -1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") a := []i32{} - index, found = test_search(t, &builder, a, 13) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(a, 13) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") b := []i32{1} - index, found = test_search(t, &builder, b, 13) - expect(t, index == 1, "Expected index to be 1.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(b, 13) + testing.expect(t, index == 1, "Expected index to be 1.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, b, 1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(b, 1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, b, 0) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") -} + index, found = slice.binary_search(b, 0) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") +} \ No newline at end of file From a406ff70636b03a91024791c22838c1a67ce4817 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:57:05 +0200 Subject: [PATCH 065/151] Port `tests\core\strings` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/strings/test_core_strings.odin | 86 +++++++---------------- 3 files changed, 26 insertions(+), 64 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 453b84dbe..34e490c69 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -100,7 +100,7 @@ slice_test: $(ODIN) test slice $(COMMON) -out:test_core_slice strings_test: - $(ODIN) run strings $(COMMON) -out:test_core_strings + $(ODIN) test strings $(COMMON) -out:test_core_strings thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread diff --git a/tests/core/build.bat b/tests/core/build.bat index 9a21d6b79..eee489dec 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -107,7 +107,7 @@ echo --- echo --- echo Running core:strings tests echo --- -%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b +%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b echo --- echo Running core:thread tests diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index f49476765..0ee2b3eb9 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -2,81 +2,42 @@ package test_core_strings import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "base:runtime" -import "core:mem" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_index_any_small_string_not_found(&t) - test_index_any_larger_string_not_found(&t) - test_index_any_small_string_found(&t) - test_index_any_larger_string_found(&t) - test_cut(&t) - test_case_conversion(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test test_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_larger_string_not_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa.aaaaaaaa", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:.\"") - expect(t, index == 0, "index_any should be 0") + testing.expect(t, index == 0, "index_any should be 0") } @test test_index_any_larger_string_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"") - expect(t, index == 8, "index_any should be 8") + testing.expect(t, index == 8, "index_any should be 8") } @test test_last_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:.\"") - expect(t, index == 0, "last_index_any should be 0") + testing.expect(t, index == 0, "last_index_any should be 0") } @test test_last_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:\"") - expect(t, index == -1, "last_index_any should be -1") + testing.expect(t, index == -1, "last_index_any should be -1") } Cut_Test :: struct { @@ -100,9 +61,12 @@ test_cut :: proc(t: ^testing.T) { res := strings.cut(test.input, test.offset, test.length) defer delete(res) - msg := fmt.tprintf("cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", - test.input, test.offset, test.length, test.output, res) - expect(t, res == test.output, msg) + testing.expectf( + t, + res == test.output, + "cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", + test.input, test.offset, test.length, test.output, res, + ) } } @@ -118,7 +82,7 @@ Case_Kind :: enum { Ada_Case, } -Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) +Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Lower_Space_Case = {"hellope world", to_lower_space_case}, @@ -132,33 +96,31 @@ test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Ada_Case = {"Hellope_World", to_ada_case}, } -to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', false, allocator) } -to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', true, allocator) } // NOTE: we have these wrappers as having #optional_allocator_error changes the type to not be equivalent -to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_snake_case(r, allocator) } -to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } -to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_kebab_case(r, allocator) } -to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } -to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_camel_case(r, allocator) } -to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_pascal_case(r, allocator) } -to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_ada_case(r, allocator) } +to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_snake_case(r, allocator) } +to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } +to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_kebab_case(r, allocator) } +to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } +to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_camel_case(r, allocator) } +to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_pascal_case(r, allocator) } +to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_ada_case(r, allocator) } @test test_case_conversion :: proc(t: ^testing.T) { for entry in test_cases { for test_case, case_kind in test_cases { result, err := entry.p(test_case.s, context.allocator) - msg := fmt.tprintf("ERROR: We got the allocation error '{}'\n", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "ERROR: We got the allocation error '{}'\n", err) defer delete(result) - msg = fmt.tprintf("ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) - expect(t, result == entry.s, msg) + testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) } } } \ No newline at end of file From 5b1ffba915286209b83c444d40f9fcb4d44c9bc8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 01:12:35 +0200 Subject: [PATCH 066/151] Port `testing\core\time` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/time/test_core_time.odin | 170 +++++++++++++++------------- 3 files changed, 94 insertions(+), 80 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 34e490c69..ec4cdcafd 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -106,7 +106,7 @@ thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread time_test: - $(ODIN) run time $(COMMON) -out:test_core_time + $(ODIN) test time $(COMMON) -out:test_core_time clean: rm test_* \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index eee489dec..090369848 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -117,4 +117,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b \ No newline at end of file diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index c6c6869a7..aeae44ca1 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -1,68 +1,17 @@ package test_core_time -import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" import "core:time" import dt "core:time/datetime" is_leap_year :: time.is_leap_year -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) - - test_ordinal_date_roundtrip(&t) - test_component_to_time_roundtrip(&t) - test_parse_rfc3339_string(&t) - test_parse_iso8601_string(&t) - - for _, leak in track.allocation_map { - expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size)) - } - for bad_free in track.bad_free_array { - expect(&t, false, fmt.tprintf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)) - } - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_ordinal_date_roundtrip :: proc(t: ^testing.T) { - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") } /* @@ -160,22 +109,51 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) { is_leap := false if test.apply_offset { res, consumed := time.rfc3339_to_time_utc(test.rfc_3339, &is_leap) - msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } else { res, offset, consumed := time.rfc3339_to_time_and_offset(test.rfc_3339) - msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, offset, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, offset, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } } @@ -187,22 +165,52 @@ test_parse_iso8601_string :: proc(t: ^testing.T) { is_leap := false if test.apply_offset { res, consumed := time.iso8601_to_time_utc(test.iso_8601, &is_leap) - msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.iso_8601, res, res._nsec, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } else { res, offset, consumed := time.iso8601_to_time_and_offset(test.iso_8601) - msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.iso_8601, res, res._nsec, offset, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, offset, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } } @@ -231,15 +239,21 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) { date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { res, ok := time.datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second) - expect(t, ok, "Couldn't convert date components into date") + testing.expect( + t, + ok, + "Couldn't convert date components into date", + ) YYYY, MM, DD := time.date(res) hh, mm, ss := time.clock(res) - expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", - moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss) - ok = moment.year == i64(YYYY) && moment.month == i8(MM) && moment.day == i8(DD) ok &= moment.hour == i8(hh) && moment.minute == i8(mm) && moment.second == i8(ss) - expect(t, ok, expected) + testing.expectf( + t, + ok, + "Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", + moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss, + ) } \ No newline at end of file From dacb0f7786f5eb1e05b06179a5f68b587aa9db8a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 12:14:33 +0200 Subject: [PATCH 067/151] Port `tests\core\thread` --- tests/common/common.odin | 81 ------------------------ tests/core/Makefile | 3 +- tests/core/build.bat | 3 +- tests/core/test_core_odin | Bin 1401192 -> 0 bytes tests/core/thread/test_core_thread.odin | 56 ++++------------ 5 files changed, 14 insertions(+), 129 deletions(-) delete mode 100644 tests/common/common.odin delete mode 100644 tests/core/test_core_odin diff --git a/tests/common/common.odin b/tests/common/common.odin deleted file mode 100644 index 021fb21c5..000000000 --- a/tests/common/common.odin +++ /dev/null @@ -1,81 +0,0 @@ -// Boilerplate for tests -package common - -import "core:testing" -import "core:fmt" -import "core:os" -import "core:strings" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log - errorf :: testing.errorf -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v:%s] FAIL %v\n", loc, loc.procedure, message) - return - } - } - errorf :: proc(t: ^testing.T, message: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v:%s] Error %v\n", loc, loc.procedure, fmt.tprintf(message, ..args)) - return - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -report :: proc(t: ^testing.T) { - if TEST_fail > 0 { - if TEST_fail > 1 { - fmt.printf("%v/%v tests successful, %v tests failed.\n", TEST_count - TEST_fail, TEST_count, TEST_fail) - } else { - fmt.printf("%v/%v tests successful, 1 test failed.\n", TEST_count - TEST_fail, TEST_count) - } - os.exit(1) - } else { - fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count) - } -} - -// Returns absolute path to `sub_path` where `sub_path` is within the "tests/" sub-directory of the Odin project root -// and we're being run from the Odin project root or from a sub-directory of "tests/" -// e.g. get_data_path("assets/blah") will return "/Odin_root/tests/assets/blah" if run within "/Odin_root", -// "/Odin_root/tests" or "/Odin_root/tests/subdir" etc -get_data_path :: proc(t: ^testing.T, sub_path: string) -> (data_path: string) { - - cwd := os.get_current_directory() - defer delete(cwd) - - when ODIN_OS == .Windows { - norm, was_allocation := strings.replace_all(cwd, "\\", "/") - if !was_allocation { - norm = strings.clone(norm) - } - defer delete(norm) - } else { - norm := cwd - } - - last_index := strings.last_index(norm, "/tests/") - if last_index == -1 { - len := len(norm) - if len >= 6 && norm[len-6:] == "/tests" { - data_path = fmt.tprintf("%s/%s", norm, sub_path) - } else { - data_path = fmt.tprintf("%s/tests/%s", norm, sub_path) - } - } else { - data_path = fmt.tprintf("%s/tests/%s", norm[:last_index], sub_path) - } - - return data_path -} diff --git a/tests/core/Makefile b/tests/core/Makefile index ec4cdcafd..4e13733b0 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,7 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) COMMON=-no-bounds-check -vet -strict-style -COLLECTION=-collection:tests=.. all: all_bsd \ net_test @@ -103,7 +102,7 @@ strings_test: $(ODIN) test strings $(COMMON) -out:test_core_strings thread_test: - $(ODIN) run thread $(COMMON) -out:test_core_thread + $(ODIN) test thread $(COMMON) -out:test_core_thread time_test: $(ODIN) test time $(COMMON) -out:test_core_time diff --git a/tests/core/build.bat b/tests/core/build.bat index 090369848..accf0808a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,6 +1,5 @@ @echo off set COMMON=-no-bounds-check -vet -strict-style -set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- @@ -112,7 +111,7 @@ echo --- echo --- echo Running core:thread tests echo --- -%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b +%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b echo --- echo Running core:time tests diff --git a/tests/core/test_core_odin b/tests/core/test_core_odin deleted file mode 100644 index bdf316228a8bddc5f4a0998789b00dc1f182e9f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401192 zcmeFa4`5|gbuWA~e;^2W?%8CZx7I%Q zoSOt@2CVOW9~UO;?6uckYwfl6UVH6*&OP_K^V?p%rlBDhK5KHX%&}B{T3IOjo6C*= z#xqprpK`7>_f-5nJ@=g4aVW17jy0?+XJBEiin#zAt(YU4?b^M*eQjAa0X|!$TrR+% zAtBqv4PbtSzI&vcVz? z_!RXwm#c3#>g`6IKESO`A0TD$b3Fd>dbR$MhM!ggp?kSE{#);*Y3luU&cRuU>h@ zJ9qT`YG3P1fAys=KI`3ApY=jfu6E8+iR75sUt z@Y%i!{@PXe&{iwS{md%#zqAVd=U2gBwF;katU~{ZRp`IG3LfZO5&!BxHv_R!zaCzN z&;M8j-?0jQ-75IjRq&^+g8#uPdhTBZKd=g)_pUi{E+4Wmlr2 z_p$_iac}Q6+uwFs@4GMQzUIcP_Fmg_SxzF@bbHS=S6mH_*KWV+;y!YG_hpw}aZL_t zUfFYTZ+Gsh%dWcW>i5)ieEZc`yfepk@9eqe>Pz08u$S&vyfY>J67WFhR+0o36ae+J z?JvFT-M!abeQg$*806|Juejve+*Q_Y`*y_GOWd+oT?~#U=-MkTyYfID#&UeVmdy=e=tC3(tCSE?jM@UB0M> zd|7h&qFVh6&w2?49wPo){9A*Sn18eXHDHZ7E`>A7HCVZ8bZga~oEkvegast5f2Uk= z1v07QbI)_!o{#(imeJ#KzvAVmtA2g%WnTV6)vwEK@$x>E*XG*1yyIC{oIriwj50qn zzh?Om*JeivKbykmh4DWvM$fzhQ!)88rtn+?!>5qKbG;0orW9WJpjQ-A_;kL|oWfIf z__U<(A(vuZYYLAJC!cZ(|C9uq%S9>ti79-03V%`x-%${%H!p?AJVwJsKZ)ceyb-t+b@372JI$u!vH(6!~ozJQK29~)L%`g5Gh%+u^nW1!kPUZ7hW(b|1 zQTc3^89L{yD!-6rhRpd1m4BIKhRXRdm7m8lL*)FZ%Fkk%p>e*V^3zymNSyCc`6({|#Uo zerx|Sd8A+1RnuW12aYM7Fn5}w;hm+%Xt=jjSja^qTX8+oRw_glu(a5_1P>dR5Znwu9dVj-OSle%?8M#cE0*6exF2!uu> z6hCmVIT|?79CL#TjzRLKsuZ|9*dO|KM#F8Tv1quXG)|=_sQx5{jZ=%UXrzNhRdj$m zNYF#7KD@5P(h{34k_>-T>@CH_&5>+28s1u(Av^q0v8^;sT$Q*u+Pt+io&R{X)NoF& z_jqa73G=9NM`?&ns$?^2)M&SttlNo;$fg_(Eamchs^bkiet|S?KdIBX@9WC8rj0YK{(WEfqnPTWGX43mt4wq>jz`k5a^%h0R2@l1FjZ zI{1x^!K--ox((=tNE$a)DRDvi6r+J^1KAfBzF=H;p$<;y+|Tj*ILFH?FeTyL*HVuH zHESFx7dTqZNn=jgyycV~zszdTXwcP-pNBDXmk$!@hrzHYFWXpNB!eZ6<+T$Ha1;yR z3v{`Wy@W%zH0G~A0Rfu$I2xX!f#;%uxIO>j2NB1Ko{L7-eYt=m)5CLhTyoLo26(QN z<^3eqZ2Y7hp0q=H&OJFEo*WNPj)(u#*NaWn{pozG=}7X!kjKTZK|X?|Iklc+sqMom z+qa`RmG1Q)tbj1;KR^(_3G3`C>-iDpr-}T{;0IRO)Uq3?Wp~`Za%y=@`EVldEhg`E zslJ6&wro`|=7_gsXIHuX>@Dp(vdVQrxz6}iKU^uL8GW4j*55uPuCJ=80B=}+{%aXr z6Fk(k{_d0c7V^|5*UTr^ zOis(NZ+UV&9NBolDwaR}i1(`@LXYG&H`Eetx|>kCd$dU>v+!yO%{Y{dx=cIgw`!H=^i_ygs6A~f;_ z?CU#lV1lY85@B`fz`p+GGoJsv!Eb)@!LLLOci9df1hYIsnL%%JyqMMnxZyYk=nvpj zOIv@O0_AT!A=)$@-SkBs6ysl%{~XQ@3aUcxb9}Uqk-JJE9Y&bzGNSZ=bYB${t5JpK60WbAI{f3x^Nu zfbp2;(_4O^L40a0b3(eyY`5+NGk>EmRx(NBJ4)qk6I6fbXt*Ob3efjY!CwDkxC;loBR29$IEK?U6qr7IlG^KcbuNapDaB8)*J6g z<8hF!^nVx!;XHJ`G#bGb?pHjEho!h2h#PvJkzB9oJ#%5pW8DFGN9;4GrZ3k}?Qu1; zeBW@a$deafG{6|?DR`Tw;g1@B13#esv?pT!4dx@<($I=EuK@R9phh=_`uoBIitN5o^% z_q0QjvXZ{#qto}k`~EZZeFri|#BZE$CYeC6@2G|1>=GjTZtf>oZm{yv`**>zxHr|7 z+vI%pyp&GaweaD^6--lwN=R-9>NR^D`QU3zzu!vf6717rrh}TbWxO(GS`gG75RbE_ z{{TV!gB5evquAdEXT_hLmnn|UemB~tKkL~l^?SnYccSU{Gm8(OMZSjddr{g6em;+# zQOYqD>mETi865)V?0){OA2!G0kJC(__oijLK9{7%Pazy4Fg2XgFM zDn$5wmrt4UA4oT>r0tk>LE4UgVsSg`f<^M)+)q&Ff+dqm5I39Dy57SD$FhFh`)}6& zk5Is}{_jB7R_Xg^WVqh9rWEu4{_$zLUJjnAUggam3RF-{0B>wl+V5 zfB#bbUZsy;a^sBF87I@v_hc-P?q}9E>Zh+{uuV#2mF)Wu5X7&M$lh$Ij7hM$pP)Ww zD4lv7_W6n>2|X!=!g^Evre@i%XZkm0su1kJvr#pJW^9sGKW&pb*B*6#cAE5Ee%28D zX|75cw?c(|zJB)`>e1D!>YeRtTJLOM)4t61wMua-uWH8>pEg?-d35XTan{eQX6d+8 z=Y_{Hem!p@MjozCsZ2C-SIA=m*2 zrKHcXrvCszJSN7y*_n}cL)JkBa}dF@AeKBIeDv|)Up1wVZhpLAr65~n*H)gnk7NA2 z|6h(lzYkgD&D!s0qu;&%uZ@mRviB>Dzd_+sBcMV=$bYPJ*(@hL_C8<)*;F7s?wT{B zOXJQ@gwM04byNAM$H$NV+4{FP+mEouJ=-E>HqDu|E)v9lE&J4^Idde~+)uD08>V8` z^dBIIE7mmY#YdS1$Fx42rvCN*-0mNI;$DsKpPT&COg`p+GDd@Mb7q=sd>W&JHun?k z$a+TO*7rYkqwev{|9?7$`TuI`)PIJ4_x_{w```-ptD5TB*)99^A0UW7Z3NjFFpV0_ z0D|~~Mv(2tA_;txQ*Y-ZeqJj$J3dYNhx{X;@pTBle`VdvE^lL|0Rfk{)RLGU1KDm( zu#NA5|GP2Gc1y8qb3ehd5)`N8iApY%JsKmF;foO@NeVJ1(1 zW2Q!Z^gUYkFhdd1_b!<`YgLFpd*w5*W806u<{zkXe(;#qqo-Pbvm;hM^<3!R#l1J{ zTCGR@2MFS?8bQ{zOSJLkeu8?}9`$uYy{abEtB|N6wKvR0M}ghJp9X*_)5ZgpU#{G(!0e!ls~kMELxFG*X;){eULr$VEz_Jql?@6Xe=v z>N3-1&s^wqs6p^^IbYgr`ex75yYaY3_YN#8wUJit0j?+gSW0pqDeLfxb%>zO;+*#A zDnb1`OP{AV^BM6bKEqe!qa|B$4Lypr@0T|3H-uG{TlhE}pHXkgUq3(*z!&^`d5w5P zW!-z>41G&=-Io{r83R0NLM{-&Z<99N+=zB@I~%W5@v`L=@mdCrEOER&;Pebjvz{kK zUnbZWJb>6?-wCZ!`$;~1h>DI?|VG^#ye)cn3@QgQqOznrK) zB&lPV{#t7KDaC1i&vORf^VD}yTiJ4s%%}Z(p0iYG+P>#0lC`+;ZXQTIi};t2oPM7F zDDO9$rN8x4P15J37Ked@@YR9F_$yylRo&8+9v>JQkNy4Q+Q7w-YuJGdqV~+&wT+o4 zbpLU`BwmPho7)G6S z&u1p}FD~)vsxv^6i*`>|@vLV&apwuk=%fBa%Fbj0^a&5I4we=*KJ_(WIqoSKfBmRu8 z$YT2iEVt`Q`9dyVhzj{adof?=Y{?h8%K1Wfd%n=ql`r)5`HgQamGc`rN-g<~nAQ1>@XNyb`HRuvL_ODC>r?+BE%)dz!T@6dbkK8h_O<9# ze@UnQibhs~-h!dOdV1M9teC;)*Pd-dynsmYjNZ+)!Y}r|c|p6xMjB)cJu!pa`6bhI z0vIYo0rQsK_QD;3wi)R7Q%*aUqFvD1UnA@!;f|FE_3iA&BQWzQSmZbED^&!qq`5Dp zx!+22chKDU7B?+jgr&cY;Y>@*F#Wg7YN`Di3_WL|0Tx1R;&-3J)5keJeY{8sC3Ncz4{M{v9NYfOe(X>BJU;BiDzF>F zX4AtrcR^D8kz3GlBH~}7VDNzx@MV9i{XT0U598qWzSQ^GAZd|!b$iLbjIUA0uW-{u z2XmE&%iyQ$awB*rg~H5f7g_<6`y`*-!<^jFaHR3P6x~$qD6&{KYgQL-|g?k3j&~G6zv`?RX{-JV0L19(OPBpGNJ{6*Dg8jb6j)@;Q$G@xMRtVxu{It3J zIkz^Q3VOZ{CBE3{Td7G){00=E5X zc*#T?+Ejttf(eIzFu}$fN}S~Rjf0y06r#ujAr@8kSmZ@Rj`G@z8lDyn++qXYUed5; z;Y7VM;oSK@09_k2hGROY#!foMgYw8evCF@?N{KV%cyJI;_uFUF9c>`2q7V(=*EYPR zIp1t*<(pm24cnUX8@6CUxV9x4-mvM8EyL5%?pws@W;(EKco6Z) zi+{)6ddi6y-s@qr!!zN$=l>}Eo<;rm_MM<#|DkC(zYPmj1=!le*i$!QY>DG^n*lu+ zwsF#r+~RQ1CKM)&CK@KtUe)jOxv{UL?Kb9uw$rG$avP_pBQYFctUaa3C6oy<#`%&? zehv(lUdBr%?*sFjZffCm{#Daa!<}r_tY$Zqih?95+*A-r8@^mKSZX33lf>vaIx(e$ z#MnW!aFx&ns_64fGa5QD$neuPasfgCtVI&{Jpw@wiyH?^U4WET72H>v6x>sq5WJx@ zPH3qK-j$+2K$O41WzE7F>~AA6`0$$Y`9a^M!aU9H;jN8buq=xG5^r>gvRwO7T$0O} zb7?~-E5r@o-!^=qVni3I5hHpCH(ZDqab9CI{JxgxAft3{)7@K!?_!)FE4&E{48#;x z+6xRK1bNf^)SBZb2Z;DlU=doe=wZNmriq7kBtR(Gju+o-M_|~FHuNqfv;u-cMQ~h% z9;j7N1xFTl-Js%-hgMj84p_=FxW__n|LxOYg@ri8bFON(N@yMAghtq&VO#K+bsnKi zJ;+mKSX%WBSStqgYK0@lk$)*Fj`J}7UC8XiPVQ>|u?#q5!urf4Ip z5C%gd#Hj15h)vPROa3x?8Wg-g8os8OZ(je0D8WqyT<+B6n*5DRo2tXNF~XIj;jhP+ zJ;H2-g{0?!D_5fJ>^}Uvny}m zny-Z)Hh{xFSPU6JzHnT=fKZJoTY(fNy;!8&!x+ToPFv~g=4lo5$vj;@|6!E4R7Y-S zUwkqD$iwH~7%n;gXdQm=A}|c2QCwk_#!8GC2n9I(D!}EFCHnQ4e&{3{_%c0)LS~tE z`+pjEY)blXry2K;V*h6&5L-8x0yCMnLSWb?{wZ?xDj?OwzD|{<8D}rU3TN*aUdW z5!Q2rbm#_s!%TGu-?Y%(z&8^%Ow(OOT;@MISUR03Oc<6i`dA#ig9Hpl`8~BL#4-pJ zb1X!WYKf|$xxs*M!(xCjhuDe$i^zl$7A9sbm7S%x<#xP*np*#pe!}hYD!fa3?Ajx< zan!NMypz>K0gWSu;Y~xYF9c(7Tj{=FY68LC)NfI3J;qtj=YV3*|CLo}!!kO?qV-&( z4?Ul80+vC*;*gJPaGvy9g8{EpS%DFW8G7Eyo=52U1?c$&x!z~7=NRyoQQ}CgIsb>2 zI{gf6QnRuRR+x4|TE7?J^SUC_-3rQW4F{u!pIZ8-3=$*0nPb0&&6>5jsO9}3J;91B zUfl1DTvHSWfXC^^%Q^r0Fx0qTk@32y34Ez7)lLp2U$1vt3PCRqNfVO6?}%Ib9HZR} z_c&-{b0Fk6J^^eez%h++myDb{{PDAwE84q5Zu!x%$0>`r}b=PZptWAJwG>jB8DR82QwMI>SG=U~%Y%Qkht=Y4uuz0dLs}>w{*3@PGTm zHh~*@PohEF8B?N#6L9vX3rO<0)FQ^)ctjoMQ-1KvE=`pKea*oFz38#`{$RN|mn(yh z_dei9%FWTsr=t7?%6jM01{-Ki_`I6I7o#o2S8RXd-^Npm9*N6b6lU2Lk=J5LfB}yL zXSk86D7YYvXKp!^7KRkjgttZ`J&hOHIYJxG5wLx3z`xo_!h7&gGUZK6URX)Pc6Mv2 z1<4@m5Q)x3x+twHq~!?Q)loVZfmNgKaSukwqA%;iW8$LW#>8UiuE>U?no9z1WLtBz z7RNO>vZ)!U*3yS91X&bvAB-SvgwZ(LQcJM%cXXB?CE|ebDfl+3dQYYm)}ZU7g2@Ur z#^NgT5Mr!P?}Nx|nnFOOI&B**pc4D|=C*x*191@NJ4`=Xquq>Y82)7r{#IVK+uYzx z=Ct63JPMg!@0%@p!^>&_TAhkK4S6!uR%0RFhcJ($_KlCAqR4R_t$u2?3tzC02K4&L zt+3C)p@{l5Q@g#A^2ITTogidt|o{GUqc#WdC&k z-nPu@!8uUr3Gu;kSY#GHMCIF~pu88XTQ&dm9~#GWXk#9dJ_O{V0grVL%^)VcmzRB< z33H5^m{t|oBifC5?Bx)bCcMmwq7kVDgF`lqWlvTqI}Y;GEa|w2knPYOW12C>&JS!7YObiR0J( z(1J)e1;c*6ONF>*RPXW-0W)-yH9FA+h)}DAbU3w=j|0Eod%bS{m(>-!-9Q7I?zi~z zy4tL`<|l^_&wp>7SpmlP^BQCyP2i4YE4jUS20bkUY_?UoR+k^V8?^JEQ};t)JRxl=f*NAFZkE}R|j>vjE5@w%L9nBB^4a)QOo(MUJ+k(XT z;q1zeg*mpCK+gI_!Eg$_&lT8Xhg0w}-N->j#h|CT^GNlC?dcFYwoEuMk92d|(oMQ( zQ?>FVEz~c{OIu9{f=u$=lHF(gwM-V+d?q@TE*9u#=|Ln7=9ao)=%jCv@hMx-L5d|1SX^03=NeBlRM^O&Nf$9?PoX+^ z3Uf_MbQpJi0kW$!SN$&b6B&+{?+zAUj}uG?&TrtW=5eQX+4~h>AEkQc3u=kHv}PTM zru^DYDL?qfGzZ296i`)PV;IKTTEFDIFG`=z{su~ZI{Qjp`X<{BXulrcfhRzdKDL-O ze0NJbzl7~;XT6(i+=?r4dq26cqs|gTlD7p0sAD)Uy%%Hiz}C{$=tRmJUCgz+0#&cx z->J@bVI$fYcKgS(VZ*`(o>0j-&<$|mHk=4uo!fzD5M&qKy3=`ckg)l@z(Ypj5$x*c z4}Zkr)5_uVSv*flSxAd&+EO1+v+?#*?k13>VPu^ATQ8)o=D5$ox%IgyOE(PW z5>5=zZ!=a48Md0m2KriZelB+eD*uQo^Y<^}7FQW|iZCv*t`8G(64jn}#N1h=m__P` zrxVbSpGwF9=%zG%|Dnz`486UcuygY1u6>u#iha8K#=(UEtsDx=-jeav63BdpA1;Sn znfc-MoUZJ8Hg(N)ZVW$&8I2qljl8xo8hKLz2Vk)1Eq=n_H(jtUOoEhRxi;ks0or*e zZxR%tWqu>lLTZcfO*k-j?(l6xcTHke<)V8q_@?+g-*?LJqfT6GjY)}q4@8Iiptx-& zk-qPM=ph$v(C(X3blF9>xM;zFxhmDAb(rZ1BKTp9xamgFU0>$lAZ%uFV~z~a*PI|4 zLvhiIaT&_tpel>Q$bzXIr#dj})cd@iW|N79HN9ude@L2WsB#47KZnz5w&T$og5~^N zwto1|=&tWjsW}o2+=nrCLupomT)~kOh?;Sf$kI%)W{aFS5xJm+_i6ao$a>e~32*1D=;V`o{S72Ma^H06nDFZDd$sF{(tNEmy#mOhFj#C|c(vZBONx#XS(+#MD01RNWO5d+r4EH) zb<BY9g!?Epp;SWTK;yb+|1V?$O`k$HO6VwZz~3(~yYc zJ_h9W&?U1O=h1@XS;r z?#RLh??Obxa@~JBSFXC}adCgA^LXPMvD0YZF1XL0YXSc*lzBMRExZkJE6a zDe`7C-C5H^PZq8F(pomlziP2O^nh-rI?Vc}?$G^L!a5SqACSo^w__IA`r?sd>(!WrRt9nxsSzP!p2wT3dh` zca+G|++k6X6DOjSM9VA8pdlPtj}WSYow2V6?@V>*4Gg$rcBUQ1To*F3l=9q^Jh;MCx3Q3 zPIYidvCf}fPWv;6Py8t!)};cPC;rS3CjRWAME7T>q`TJU&vr+Nj0OwbIYU@SoQP5q zSMsOzDC5rv?Sns64Sz~W7_hutl~zZwnZ%}45jj;6QB9S!KWPm5Bzg>g=G>p0#`I+i z<)K}(AbcV{Fj)l6mrYrmd|7au>fp=9I$wG@?aLrK@uhfJcQ`E*UuFmsUp7*r`!Xl# zuC@7ci5-I9L`Gu;?!bwhI1!~JuH;MWQO1{x;2nG!j3Fgqz}V%Q!IbjPIf~8X%akf2 zrz#?NvS_(s8N-52YcgFze+tz0LF7*|E84fV~5o-gTcpo-juz-k&CcCKDjR*pOD^xaLPm zE1R0@#&n4gN=Vy?Eo=kVv59aZGJDN|#lil6+vUTYn1%J*9;(c4LOQ! z>&1(lDv78jo<^V=w0Y}exNC-Vpf5@ee_anjh}US_M+htR0OEhq3FrRmWw&pK3 z5l%#AFv_X(SJ1r-@=5$l9H|O)Sn9xEt>l*#-{L5?trstHswASCc&33Bg}!hj0W0r{ ze)AI&*mctB1yQcLB^HIZ^wrbUF`_uw0r?3JYR(#Hw3~7qOX3WBP?GDv zNQJ-yzYDLAvScZVr>oD?)78|Ok9VpMkMeXiS>23(xh<1r{Iv(I#h<4ScD5EvWKz%DpcqDA_Lr&GF)|T}nt7o-m?f;p30P0Ai zOLuf_lv<%Ld%bU=Jj62Ys}Lq<@Pie#&hB86O#oarBQ8Jtos4NWhZP6|(9f%JCR@8( zUy&m3D(c9(2e;)LQcpqM`o5F!-HaEYFYychmM6W4Dx&w@{)q^uEW|9>=r!7~eHU>2@VYZ~-JZIR zQrBf%+len?Ao*e}%?e^4C5U^vlDDta3R>lGg2;vh5l#T9uR+8@mN#N7V)n6b70yDe zK>(AqyN-U5ow7Ns`B-8Lq{HlO6hw9E(3BYTEc0 zfVirNhpj3@Xr5%O8NwuEZKp)fSR+Yytu5^-J4$3}?r^!ti4##u;)kd;3`A>$l+=2p zjMa>UodS4gs?kJB!hkKyRcUq^vgSuVTJQEZdx5M+y-vPI;w>63^)X;(`;N+uX4 zOH2HhzmJ1s)JKjtpV``>C!K`HlShI$<`=}8M31{O;U zzUBn^eX{0%?lE=h{x{D5pGrgFoSz#h7|b?hd$bv)JNtmw!=Zw{2SyfZdo*uHN3cCd zydYL+Lhv;M2Zy+uF_Gjj@b8gHEU}|mw35iU`Do7lT3`*|RmD^LH=s5nfWqpZpij6Dx4THcb|^ zk6<{4HBU~v{>=pms)V?flF3=PB#{ip&4Dqli{Uyeq=}4x zB5A!4HW5xlH6awiPElyZZ7xT_z%9h1eu%?t#E!`dCLX>=)IMrRY@ zL}d1$95Z?lG7}g>$aJ{Q3aOG2P$aDv!Y0Crs3wH7Ng)|Vqfi>OPsYbL_yn=<0*wFh zJ)D9#D3d2}pVuonJK|eZS(_qFA`0#TQv^{e0Ok{f=)*W5);M`aIQP~V>!FO}eG_wgFUw7$urHmM9d$!zRLs$V@{yb)E@Y7eSu! zj4IGosRPe6xvZ4JX+5ezYj?{to@?VN|3NLl<2#}SxI-TZAqMQl*rQDVMLJ=C!&;+ zw^S)ib?cE1Tg*s!ybioG)o3CmF|4Y~Rhe`Yn`Jr*RYXozL{w9STF?lZ#PTzqy3>RY z5B>!=+IdUx{%q=!!(M zE8<~Y;S5atm?2F3I7Er=$BLx8*5=1PM~N)W9TG)OoQP5qgCD6QK%wvsv@GAD)b|bKtHz2k2sYU#Ioyt)mnVf)s_w$5f*T- z)LPu4N_bDm5C!n%9?jlU@Es<62MX88**(6TVB0Lpoq+rWWc99rV_0U50vGwr8`*&^2O~FA5!FTN zUKYgpwV*wNz#T#h%G@Eao;#QJlL@hm04l!_gqnnqBRs-rq%w~61z}Lp;eAemcwSi$ zkI@KXKmt;pL8LS+L*9%ad=7}s8$tS+DRl=!VF-9dZ^DKdUY3R0=bGSD2W4&}sfl*b ztVhrL1oxGC1Tm@rsfj@hT$Z89q9CG(=#U$8r;Z-040$o}$ka$j2Q7&X-*(iF9^ZZx z#G)pMMatU?A`G;8Infjbmmy3-UJE69 z$ZM8#*V;l}(NQ8xbH{)XIdLLNNem&6DutM4JyOUsBjIctyffACi7Fq%2|_ec*)xzh zi<2AY9H%vb)GH2|ifhDC@SlI#NcKnzFQm?ZpP)uACU8j@~16N|< zdL|~56oJ9hLXU%Tg+SY?!GNd#IfyVpBSB zJwZ(y7>%SHqvBy>v>CW$f@TPl3EE7FK0%9;?poUfZE}>z(%dmCL{6NDQWC=irAlE2 zT8}hA%}7|}z&le7n@UO8xN*5EIY+TsrlU|rcE&JWmm+*y3z<-;>QeO z;>Q_EbU#i@x@&EItU5|$Y3?wd$cYnCO5%rn&QYb{DC?2@Xhy;+2i}=#7)VOOj}yyP z8Fv($WjYE~L{3#iR8xgErxEDKSbjv(LWuHM{n5;4r1mZD@VZF61@DY!mTz3@_JSo< z)`EydtRvkE=AfYM1;rey5ku7lQRY6fwim2Ll-LcOiYN*{XtKyE7;=o(!bsdJh{RG> z#L_Ql5oIWg(@3m1PIch7M1HA}=;icCG;J_^NJ?jjhdE;oxMU<|2$PZ6M~OZXdnDbp zwvpKFD3PVP!~G&BPDCk*AEGLpcYtB2S&uXl%}7|M!8=opCQ=dx>|Cx&yQA1F(^04* za;hStnkv+SM&QVstBWYpuBExrB1-V%jM`v-OsRG(Wavaq?rc<5UOP zv*efZqnFcuG=layT%59>-5xWtbc!o-h7N_0OqNxExoek?djWNGd&p2&$4QA*;6 zs0u7dm4c(JNAjZ?3E>>PGu3D!CE>^1a#fbrldCn$bQCy|Qxy@_RG}7jBYc z`E%J3j0GCRIai)8~9f1jr*8YQmoP5RaaB3*rG9LCi5gYGM%6m}O`(CkTs)4&y^dEf|?B zL-ed5o-q|fa26dNywOq2zEYzgd@l%Jiq0V{S#*A(zS+yx4wm(@@k6fUWwon2f(d3> zlnlH!i7;~n(_LEf3b@bKTu$q!zDhZgFOVC-d$VU5t5054m5Qny-t z>^C1}wR(>#A7!SnVzg}NEXsg4FFzf`F6ayrzRHkeW*lUC%!i6|v8ggUAeVwv?wq0Wp12Y`2` z8cvgvFyQ!dRmL2}W|@vc6_HaF5!F=T;G_}gTpMMzB)CF6>eEYB@Z*r$;Ha0f;Kxxx z&`JlSAcE${N){(S_Bl>f;eO#WLLz4UBN@1^Y-iCgQW~r z;=~?`b0>C7w5x1R>~fUI(yU=QkrOAPAZ|d)pud2@Mb;O2(9DBO0gN-%FsC$x2iuqH z5jl#@GF^loBBvfAay|S3*O2(uB1K3)bck@po3fF@)-o&-h(>XnDwjtJTiI*}KMq2! zLW&q+^Np@(G_10d7j0dgaA+4IkJ_gqkHQP;GxGedtMoyg>4j07P3?R2_?iMHhLd1& zVzFs}K9g|uIwLL0~l>-T&olvr5@9zOUJPRk>WLE}4dFc6j0t`z9)ic&tod zyK^;e`+QgHT+gfHYOD3i=lrTmWdde&-ypy_bcQ{Qn$NiNwa`8O3k!^QXx?ez z4Js&W0|%fzBSoQ`1*OO+psAYH!=qe2S0Fy!lJ`uv!h2Gr0dia4>oBmBLE2cY6W`XQ@^FqdpZZw zBBh1ek*DA{bQ<*oMS80}_Tzr8X-auIm42eY02{|Eq2^D|7WhgF-BS?1~vtluW+6m$@A`eMTe6dwa2LN za@ETU3J^N&!fU)Z;$j-ZQ1d(qrRShpevY%+> z(9pYyEQ_t3M=CVr|E7A=b>x}fiXS^$#K^^Y3Cf~EaPD($32jo;eq*A?>uiYOo%rDilxJf2@SRaM%6v>CoY|cX z|IQ4gtKmqbXQBK{5$&|bMjV%tHy(EF0_3M{|ZqJ+DMd0=>n>6>`PCPz3f#LQzolVrj0vPmqU zc+iW#Sb*R3#L#sH$7q%!w4zxGRF=;G1M>5zW+8FB<%?#PX0}APN70IAAxDeHOyWi z?aQq`Kk#?!Q6iSD(7J~ZW#f$Pqsq2=cYB>6XlEINYELZVS;G!-vp2fO+y19Nf%r;c zgZ3RsePIV{`ksp&{e*u}7 z`KA^Awiwr1sDjwd0y9=Iu&l9X{?Ia#Bs-2M$g|_Jgu1ep9k)74WNE&TC350K6l6&Q zdI2&+9&EkPIgqO1_7*VCRI3NDSo3lvijHENOy{74$f<;gTnU;(iAk5-j5U=I`4Tdl z;4a&~n7@?Z;F(48hi(NyPu2$YU9U zI2B}hBly8%jP`T#9E>4|&8px&)sz1c%H&_<@{ioHNe~b22x9j{2>k?Msb(*SdM5a+ zw|3CS%f=7W#mj2v@4Ra#?&fFgyzsi4u6^03q6U2vGjc+MIB#Nk>O0SgHr*B7eAB;u z^VwHj(0MEDjTc^{mn##WCgOz+o1XsH+h+D;iD9~QqpA7RqfKAq+j1<{CyOd3CQh0xE0p%_)YpVc%7!0@N!@Nno7Tey)ZBqgxMSaCk^FFt-Co4>e z8R9V|s-%PM1^vA!wBHJuL)M)B;0?@Na2A3ncX?UnM!DO|MM9K&ylnh}+imvO?@nf6 zrDS(vjIlV00ajMQeKu}j+PxBhUA-V~iU`8QqH_oZ#=IQr$GvRru+{Uj@$-BId5qdU z#_og)bBM~lu&c_DStTBjp^TGui87GOECPl%n#Q>1hdC3kId-R2c6!G9a6XE3iqQ)! z#}&n(wxAucjb(9KqxL#Zb!eyjI+aj(IenWjNUu^0hH~*>D2G*mOKuZp2$RfWloEa6 z8j^I^+7_;gqePbG4*Q9mI1!~JhTDWxDXdb~BPAC^)4qM+ovDT|q$CX3vs{&KN3mI^ zqfkZUR7FHJRWxlV4m30d#VVSo-e@W}rrbI1^m6}*z0tIbGSRkEurBfQoFkHcnEitM z{RM349H%;zC&)YHPcNta8N^pf$}WkAb!iN^#Ge_$#GkE{=>BYxbl2Ma+3YBhrMbgE zA}3BnDTyoj(|VNgXA!)EKUEEXN=f*$X}Kx|N3mI^qfkZUR7FHJRnq>XG3XO5laJ!h zQPP1Yr!nVdBjur8PS9`6K~IQdd}8gQzEFZYsf%9mbF`!a~Gl9b&M59`hl zaEUK7go!Ve5V|jwy`dMTrM~?day^lyxq~c8_C%DDxRNieM;Tu-9}K=!H99UOVZbWm zV^$T*5Iv2oQ$^&&iKwOueMuu|o^VASsJ+qD<|OPTpS9Tr&L*UMH=z*ihja+hV<_W) zsO32K<6fzn;N-_q$Egl}tgs#Z=r3FF@|xb$7j`B)HZ}4WV`A4b|8+J(p4`U;Vh9|$ zE@%@>HFymJjZ2bz)u&6uOhOt%3}FnARRT^#G!P^#ymu1l#0Npy9w3)jND+9Zo1C(O zyBx)a^@2rCWklqH*{dGx*cN3mhOV3AW95xHR9{ZR+}yd2E2(@X9_ zBrgHBDT5?bQwAF`LRh&)Wu4jkffa5q;Mo5{Z0Fw4gm>r^nTO9I7=}Z}jkrn9;zp!I z9wVDQGqzn4!nw)_&ziT{myiOLq=+YO;*Zr`pjR>_KOqm{wI4(f2jtM?7+y9?q3~ml zobcIn2RAd+5I-f4x9i*MMyX-5YnuF;{IxCkowDSYXFy zpmx|XTmbCxFch!^<3GkO$PT8tC)n?3Y%5d@_cJC@$KU(GblHhWup9Og#$yV4Re}08 zp>Q{}kE@QH41>$Z>?B7IK6s1gVMqpbYTsbD9Kyn7p2gN!9!1_t2+Ih9Wsk-dEamlm zUJfIo;$`D!VT)AfpU$kPwSL*k4|wq=oNXhG+eX^)uLs-2GPa4H=40D1F23PF&yFLxW!eioH-oI# z!yV9C<5pdTK7vTm1#w;tNcppb;J-vX_%8|~r5CiL81kP(nfzH#{!B;6-&SE?js28B zSyTNfK@klY0tB(JuXaGNoNnc_0i(kgc*%G8fhc_}AfuvFqcqmYQ}JU^IPv`;?lio* ziM|k;p_LsUl0k95Q4kOB2-@i@JKlpbJI;D`yzCuskz9-!^$XEO5b;wG$2@{~zLyYN z=pI3=OoCWx1T9IXoKBP}hxL>*Du@9Bh+##*F$7-reyp}qHzR91@Z5q4CNIb8W+yOC zyV!z_vPNLPJ4ph)+6A+Faq3eBp8Bw!`i#l(Fdhd6{7CrdFUM*n=jD^Q#RWDCVn7J` zfd|iMpl>+Sf8Z=0)$k^;S1`0Ri7yV^AN@do$H&+fBUb&#NOgHJ%P_#4;|+$5x*fOA zdYvF>C)w#E3Jhq-4+8_C0s|le1Hcgm2C7{FIa||((a5G~HSI7QUSjM9=`FPYv1zkUpKj;Nc`5;~>IdYEsbl#SA(KkKjG7K?Z(+ z3>uS!kkcso@V+w~|A-Gv4oGH)qwwi`z4PFh<7+Im(EGM_4}MG3w_JuRfL($xy!c@v2-=BJdlsj}IdYup zz^uKtm9awlp)81PgE;1xYG*lSbMjD@*C!j$GMW1Zv{iy!LCd0B93`?eQ#e@U#EHlS z*^o%xoI8MDrVbTl8mG8ic9WynCetCv7CB{$$YoPX3*Wv8NA#AO56|GR z__u3ruuROeFgqNFuTUNZXdvAXE-+YFKElDW;Exi?(L6}UBg0xA5JZFnR|PshWast# zb{wed_ig(RS)gHRiHT5wT-t(aaG+2Rs$~!}O65JJiXaycG};S}n&yHa62$UKh?TMr zkRLJ>I9)~AMKDzI945jej-FytdJ!bTNj0u6d5mu z2jp`l8<$?;*$hk(l<6CTSfN;kZ*p2O!*O3-28&_2D4${V7eqDydajs_gpkw8<%yZ5 zAXZjE&!i!*jIuIKLddHKBJ&2s@FYkthDdw;g-CE+D=IxpzyNn&*OyX^DOa@Gb8BlXoc;oETg?3%CVQH zMQ524>#ZH`qcFxv&!<&5h?3R)byCQd zd_6;A&(GjF$S~kLc&37LE=|C#b~8TW&_H*~f{6VZDsa7^t?4aUoYr)$X<(*0tm$3p zHJyd@nhvqdA-UYCoh^5@pQH8qWKAzpps(po67CAxnqF{}$kI$`v8h+H<^ROE8hM`r!ED~$-rQ9E&-92I#DI^p1fDAWPR>-jJK zh{;g}Qxkj+EfH9L-OH!<`DXR7kSVW5rc$PG)a^^|HLx{)>is(Y`5?lQPkFsjn4PX) zU3I?KQ9=Obo(z0D^9B9E!lGaae#~EN3R}6`KSK%AGtjWpT%f3p zAj-2|4)t?ht`ed?_OkKALZz}V%(w+k@67jAM?b`lQ-h(I z-#oA%f$x1ipYm>Dx`&imZ>`7X0lV>{9!llY80jDNiBG)i@L?RMUPrSw;NnfVcyq($ zF#nt2^Cbw{Uo$(dI_Q93&yk=b5L}*l|2$pJ2j5%TG`nzBrn|9U;t4j~-AQ+Auz;Y; zJdJ5q*5m0^9KnxNSdz?2`$|r3JL+ILj-vje8$Lz5{|W65v@G!tfR1UK~)! zI-X9zdsWox?bIWM_<#H&QzdWOe>=N_?AdMVszIa_N2e?YS>3=-`44=_091P*0vUq9 zVCmy14c$=s7{OraqX3m#R7#YzgKSAh6)6xRD^NyW=3IkbSr`# z+*`DjdN>X6#|~{c6XsCOnZQw}SSP}#g;=R2fW=NBC zMH=As3KIx6*3Qyv$}@i0ayq@*`RQz#xcbxCRwp#L$MW{oj%ZgQUiqBXi1!&ssHSpq zSev+-V;lCz{L-jR1}wtHUKk;M@25EUbO&r_sXOtgo@3XD=A*p>Q`oO^T_c?jv&%=L zfkO&e&C4Pr4=KPC$x9o5SdZj~c-u~>Pyj3KeLv`BeN`}3zWe%m-`D-}4un>eP5lnRY^mRg2p5JM2e z6nJoWCf`eI4|QnMj$1e9vKyF89ek|07i=)&i|j@-hu9o6!L-ox>`Y9l*eWqNf8#V& zX#ADq^B*oLIoZlbrq_MB^ZNGO;ko4Y6(sbQij+K9MS|!zt;Wb1yTb`UezxXstZu3z zsA7jThI0I8pMNSWz#IU&mAL^UnzzEn0e9i+>nw)fjV{2EG>>-M@YK#yA-?&GPtDEm z(vTGg@;bPs5mb;2G?uk3g>|)tTVavZX6n5w^x#+4)_PD8A6vi3EZqav#ow8z?*Y;5 z!4M#NFv{Yx9?%!C8J!C!!Bf3(IEoQXSW|!j97CoDJiz4JZNqseFdgm2P>gfvdD?cp zjG34ayE-_O{in85CS%VrJUXKRME_*gz?r}u$ldE6SkZpWPu%cFXymeKw#*X=C;$G_ zz#&b=Mh@vhd1w*C`#H1-)B*y=cn)idC9a*PBa8ibHGkvcz_%LW2iG-V<+;6CKhR#A zU*dNTer3KFEHFuU_s?J?y-dpIR&Rfvn$pa~Mw^sVFxS#Tf) z!l^VgT!}081GpC2z1gNR$DaQ-;5j6P0L*nJ7><1bu}vDIpRV#1 zU*HnTwgb1f(f~hieV(zZKKOncq6R}*JNvKL*+pY@(`-FlVtW`?K+wkniHZigO&(7px1$xT)!k7X@)|K@humLEH-vv}M|( zR~0Cnb>0dfelvnb$`yT#>exl2z-8)?XgaUbhhVU2wSOE1s03}`|NggaXpV}<33LjoC`gPI*xN)mi{g_{) zeu9_t=N!kbDoBLq7uL_e1YC&3cLOwd@ujl$dborEtD9cvV!Rs#1Dc&GF_NHU5OCgy6_CRMdzdZQ;q1oEIK*H{ zJ|J*;=e`Ju#G!NOw(Xw=9`V7sex8{*Yt#<2-?zo+)VP$e=NGi8U>PH1ub}UmL2pXh zsD5JHeGyKuG>FqqQ7G|sd*$I=ptdF)C->&C(>v>uy`PW$g?ONbj%S(%^rKm0eW`2f{~!_l!3Q0H_~u=yDI&@9#Z2@ z#xz9kc0mvm@(ZwSQ=9J4#p|Y6UnU;=VZvPg)7H;ta*YC8CGC4u7e76J`0x>j&lYL} zzm);`v1h-E(($MQC>dn}C^?3Z_(TLy+HMFMG7eyIUnQN-9y|=$&rWAjRwfO;A9^x> zcp~zLuo81>B-sE4>Jywly!!v=`NON9u>9dCE9DP&U}_z4w4Y#nc;fO0{XG2>l|($} z|3gW{8-A`y1m2PTxRZ$2Ale@Bu+n<}#3d1_VI|H_W)er4N!)SVlbHlEv?}Jos+j~1 zn+8$O-0ToeX?B)Ic?7YSXES3=9Y>k3?Bz~&j3*J;L!E@9BAm(cTxQQy<-UfAhSup` zByyzH@nM22E*y`?fCg?a;8b!em}F}U3nxVftB3Is$-+}65tX5alS8@-hDIGY2*w#n zOcmzHY%ZFL*V-$Uww7jzndMFIXliaPX=WlE4NhCPc9!NE!^^QwAfD$LdeRCc(PosB zH`Q+7x&U5_lsz3y-M2PsnAT6aZY@<=5wB1&$%<<9qOGM#KLxN~$NOALimNq2(h16( zOJv#?tSECW>UQxP2A?xgN%=2u*!Q^?ZY}+VPWg23^~(H>z~ygjEByt36p5Doq#cPp zH5e6U7+N^VjH+vOZODwR>xp2%iTO+52D`&bqYUs1+qjsY{VCB@XXa6~o{`pno@j05 zKZDkP4z2&JPU}CX)_?XWT31q9S4?^(rS(iuZ$?`C2ph%y0iAAPm|z_9R1o91$PwL4 z#hU230>?3mstAw9?elZ!aKeV;Ha6`clWsP^9~F6u3>*&XxRZx{A~MDw6?L;ww`O<; z5iMF-+eHTD=;brm56#4Te}ch|hAlj3ZV8Wz^j>6?7#8Jl)QO*SWMiRLaBO4BM2%*m zMjS-b0Gewpp^!TNgeB(4b&eV=@ehVM|KKX7LUD5Z`dn7TmDU6S!->eoMZ7R$)!rQQXE@xh=2yxM(3lO%@KCSR>1if`w41dM#3<}^-)FBxU zjnSqlG%ke3&7_fP@UvX3BF~rMqE_O-KO>}OkMHLjXz zhkVrv@iC#Om%w)k0v>|*ADa46;R_9$st@1RkUxLwj9ev`L(QVqJTp~u7_A559#kGe z^M}qjeE2$2#xVY&T>ewC6mGSW3Ydq(lztlTc`sfL%5Pqtza4*b`+OjsWE$geu58v>=N;2j>b>_g&FsuJEUSPj_(?WS4{Krv$^k zn4@Ec6jfeT@pKCR*2-#&np#L)_!|^!eZNIrxkX)JhYwBN_mmT+Gd7(R$t2!u$4~lx zMAL&Dca0Bn4B{`Y3lHXhvl?HF8~2zJyq!{`*Qkp%UgOL2XFp?W>8Ck^KhZN0KcOog zd6cn>v0QmC>M@op7jQ&9a`_pDugm|{^wfPP*TyKAJR~8D-_QSsuA!D;9?GAE*5PRn zj--b;1g~Qg=a;L9Uurg-h{}ZLL;Gv}X-9|Fl^}|LAY)zpc0|WcgcHk0SiiNjM!KxY zH&3&#ZKdOK@%OPNb?zU1qa`f8SWgkzun4!|3OY?j!|RZt;Kpop0uNv{9!@|Y;S%x| zMMu!Jk*>{x-x8ZGMN><-dyLt^KM0hp6ds`*#ST<#nu#_&5N&)QIu&E4)|kmq{%=s8 zypH27zZIk90esJLstojPdjH(@cSrg2r#Dq^u8?9Q-tjUOoq7jX6LFe_w3)+qfah1a za?DWr?9DF`?Ss*&(>`AFgH=yZG-k)B-z?P~&o^Tn=bKOdhkw|BLGit*%k!&tJ$gO@;{NhQ`Sb6@znN|Q3+ns8 zEbjAExA}`9_uBQ;mcQn!(zb2d~V_eA$BkmcSj4^Nf1 z4gVwfO}FLGzbE1Oui$wQJRi9Tp|9<^2cqXb5}oj1{^PghZ@fD?K_*65(KYs9ad>Lr zzI9XcYhaBUQc1-CT&aKo!kUSuVV{{-MpHjpQ?n6rn}<>z9|2pBvrvqZ&1& ze$@iiR~O!j!pu$d?MCQv9jtzXw4&pe%0quw9zsKYd5stOKRtkc->|7#e)u3p)AB142IkfU~a^k8^Co~fJD16H!zzp zDvS~4#<%L`MzE;JF*oLG)DV!&jkl9E!VIF{rs}O0|Nem^X$V1(_XH$6-ZHebb!e(> z_-pt^953>JdMDcqZF#uLN%D34dm!rnUIpVnLcEcd=N-UILEH^W)@DivCdmD#K#gxA zAIs;4r-o;C4>oeT_ym~)^a=77zMR(9CoDgm(XefX8#z}BoGVSy2vF!nBZo$TL!*iK zGASa8Z)MbPY1@tLHl8dp#^Q=Cnz2B2X8E@bhouZ{z`xg+728IDM~@+dcu0a=J-)Gm zBxv6pzm}4+{KI}}g)WFQMToIPhllyXu1UKj-pLsLhiK}sp1bO1N4p|Ka7VyCJ4@|6 zKI@`--O;8;+BO|%+jt;4^$~UkM$t_rJ;uh3Row#HLf5ylX_=}->}?DC-D>8$_VkST zKms|u@4o5IXwzb}aWOje&ae?0ZKpD06gy6_6ZFL-jgLP{9XhyB_LAhqe!>BYMT=ty zt8*AlI5hU-18ewlpz{MS^+g%Yoza}F>`)gW1aE)iVFK!AB7LKK?XGorp66@j%D0aH zLPL4zfxGYDbVs@J-9{`{7^1~#JSN6$9R~pi*$U?n^J_gp!^p|+mO#BqDsO=bejh1)ymOj1fl*V%9;lt(PgF~k@ zt|p>_#x8&0zT+Wew?yogh~G02zb_HL2NA!Yh^Xx9DER(-AuCK({lVnb zZXqUeGFdNJtB_G$u|={zz$~8x=_k?nC-}F7$e~jO;CODZhz<4Pb@@NA{mI)g*_JSk z9s+y_6Zr?2L=SVW{;)jMTlxkj_5GNE_km)Lio}RGIgoPB-v`qBN%{@C1Iq3!&9c!w zXmmFk-GxRoR2qLeO0(SAFCNCNks$j0RA?$b?j*i>h4Pn5=blu-^$%9mWk1;72DVe6 zs8W|{Isv3r=rRReZlf+b%$YtiT@Dw*ueI-Z{$y@8m&2jgV$kkXsr#=rbsHga=|6Cg zq6$E#%n$B@9@dwWPMTw1ykn{EgswfjV%NTiu6+Sr`#c&?uxpci35og@EW6H`$oJ_+u)T%rBkSajE068&tv{rUG1XF%`;`(*iL$mYRt#QULzoiR<{gKjsOYPFej3 zu0Rq^@D{roz?oj^`~vZ#yFA1}o`=SHbm?sr(Mb`0%vZ)2JX-MmQ}U5K)HideGh#q6 zN<$k=IaEhfr%ZLCc>Bugbg~Vy5dIm!L+9!96zJ28u0IsTVl- zvs1<1Q*Qlt(s1C!4W#ku^6Y`7VmJ=Fnr*7_y3aRgk$>vLQ%pkgJD5j2l~=6Ckjuk; zZKdY}^SoOhOeBAnr&c5D&Y1a~jvSBppP#?+oW^)PWTmms6ZU(cT{r>$dv<&ZG~%5H zv;Wua=&O1EDAybKXB}j&gMT_+Q2}AlbOW*Z8<*n$X7ru=!E0SS#FxurR7P}p8JdUrfR&V_cOcoE?@zcBB(bVB1YZv#Z( z-0ihQwd-aV2->ZLD-6w(WP3H?GlrNLye{lIX~R(4y6v1qLX`arT;RO$X={HNu{zBR z-8%W+v!AXpZ&QqI9wu>P!j6v^?`}I~`=r#~h{aRux~J?gDDHv8U;d%l%_E3EEN|cE z;BcmWpgQ%3eP)Civ~OSiZ|2?xKGLeZ`=5nf7+}|#MZk!wj2dfJdRMO3*jTZqmfBdin%yFq1!N;WYSgOHS{=0NoknZa z3ahp3|NHx$bI(gQS@8dSo~KVfu)q7fUFSO2b)DS863aDv(iU*b{GRk z(Xy@lv;(^}oHJ8NMM|>grjq^E50Nag>@jzd)`__r!+`q|Rmpn?-D_-y^$$-pJe6Xg zd8<|NzS6z-wKV?Rl#P99Dtk5||I)5iDX(WSwKn1Pok6JQa)wc@%;;C%j^OXk-u34` zL4$aY#C~q8*ezWo?w4IVI=hqS-xI`MP@_6n1WDFy{tw+*=<7SRODO(&O4Xrk@~?a{ zIon0< zstcy_t#5}m&;InkF3#Ml@iuuC3@p+9XLTl1U2283qQm{1AkreX<96Qlb&yxz=}jsQ zqcEa_yyEq;AnyD=q=oVcP(~Cg$^<3m#m$c#KumnCZc>%q0(FS+eH}KkC3oHW&7m46 zG6odirf+<7jL)UUIb6z6u)m+O$#=i6J{@9|V>ce@uo9udEUp%A*Mpa zb`JdT*d)s^y!eeA15)YVQKtu+T@YUU3~|b=hV!yGG<)_+Ldn0%L9C!t!*mwkDLTIr z$7Zl^m3+IE?<(axW)u0;8I#Uc@(n59wpDrf!m}vI(Y#EuM3-6I5;NJ1q>g|%E!$6lrAN%ZlGNl8tJL}{%x&>={Ib#P$tq(-yiJvSN^y^tgk13(6!#(G zQVIFQK{O2y=A4mDx6@5EGc6_sDvvQuWq zo=gUrU-AkvEb1FI(V35rN`?GS1{-(pP=@li*cZX4Qurz){yctPbQ)$5cTUUUL z%&7mKd^Mg@`&SHzL*AX*Ugay+A#M^JjyDNz!&Utejd*>Q`yub758`#p82>1hdV7YO zB}-#3$d>j$1A_cLnSAt$Tf30a{zntB?>xBQHgf`x+xIn4eL9q}GA z?utJl#2%Z%9N8%wIgxF=q45JfCBnfu|E)eV9^V;`TO*tSNvhx3cZRdp97O9OthXpi z({tE;iO<|frL)*?x_>YwR{Dx+KO-yU z%Z-1U_DWv;s^3XfRwQ?p4*Xy}VtX=n@?=bASGcYd8D>NY77xjmVNtlM3}5@Rpr#2G zz%pE*y57T(PA02xmb6A;22w7OaqhiNNgfA@*^Y;%T6vf$#$R)|OT*72WeO|hlxtB{ z@>6m>6gQd*Kl}pFkzM(vQoHIC6O%BJY@)}`zD&^%j?rpKaIF-nZ1AcT;4G5_q3MprF>xj-($2}^#qaqOOstO zpBNU~@A{Yf4(C3(GhRB{%OrjzIW~9Qdr6ZYV@|*RGhNqdKC(DM-0T&qTyv$QBi52( z>`8u)o)9X3eT+UN4NYszh?An(*Qlt+mVfz4Xt(p+(3P(P47TREMtQ8r z_mBq-*Q17aDuI?}l50Z3-7#TY33Pkre<9&}PI%Jn`x>OqcXjDhX*ip_PX>SZ2^3V` zs*TJ;Y>pAW8ex}naUvB)*p{Y)95>8sZ(=UhuoqpcP zdcYJ}#ej`|+v+%n|2+uV1(+#{K=LXgTe|Tj&J!INj=@B$&zei_WdvA!9^B8qNp0Rk zzQvcJyCZs9w~{aX=lc%NzDu&Sdhh$O)iQI0kiSoYeH$iIsv_j4swXyu{FzX2UsT%% zMbY}anS>zylf`i=ecWx2=*iIK2y2oFlAi%n$?G|lyq}T0@`a(v#U0%onx<6(YEv%B zC-LB_5gZG(APr3>8QY{Ro+DrK=nvvo0k+^qa`+qiUgh(_Q3lxPm`Y zZ<y);6Ie ztZme|(;v>syH^OWDvJ{%|1O}F3mKDNEFpqFkQFt>a<}46W^5J*sXJX|44(Z&L;D}2 zNJBR7>>z-4hQ%2qjOjYdJ9%M|g^YOVnH;-=#fm(c0;gVmnF2bY$t15I3gLI_#yN`G zsNmPw9C1e&*nkXd=Ia>PtcZB& za#bYaF|9*Hng({E(pDH)M=Ajcdk%s!uo{>en4VJu^D_pfe7W)4m4cC(gp*b`vKi&W z$fk`*REQvis~Xvd7lYVb03#EDWP->Rmv*i5#3>jq%|zwWcr$T$_Abc-?~_Sh9P`Ob zX-?G0H;F-!-27GD{SP4>FhwB-vT9sD0Zz$U)d;j?lqsC;q=PK>K&6K0vndIv+&HTSj zem%|N?qD=Ed*)U(<^GMKda`o79s0ItQ1RX zil-!|ncyxqeu_8l6@&2g%fxMb9r2sOt}gfK&+dOqAaj>aeQuq@{I;N;Eze1Bn11>ZoSznA zAV7~$-x$M;O8nGZo8A!@uc36hd5eUqZjEEZ7R~pWeZ02%Eayp9xu}gIOqx&-HEfAl z()Rs|6H@xoWsSDVa=*l2uxvVC(C-I)<=@zOAGuFN?uzFp3yVcf6XB$x^y$_WM!(U%v?iRxm&0;Rjtm!Z&9W} z^5`|+&d~$30s1>RDZg0MIox(pP>m^6xRr7v*n8@BOx;zbghu zm8N5?;S_1NxZoe{&bQbNSyucO^+;`-iVu>omgy8*KCF7MY^j)gSL36qCoi4ZZVu9A zkD|I#3mH^YVNsK(#Scl$|8JX*2LYs4-dBOyGmmcAmh<5K=0p#+T69b=S<7@+hz+%D z2?av8I=}AEj{) z+H#t4_K@A2ekbR~)=uoqUFz?LB+6xx;vWualTt(jyiW-oi^s2yIzBmI4&PIG<%QJ5T@6j=$FL!s&=w*ZNIBM6FVD`4e;bi}ShsOA5LC zE4y>~-!10yZ|Ki$-5bf=9Ld}i$?Wm+IJb3|*RHxhN@P|pf>q5>tfm}MzEJT+<*#ly z{EE#K3p==cHc=-iY8l#3@IVYMlbIVK&LP%?3la}YtZqf{r{z~(aZoCQx}-^Ea$VuN zVlmpUct{tGVX`k8lY~!O9K2u57-W;V_@i}w0Rd)l;FS)0=ME( zcEah+&5drts$21rasl)WI>udobD*1mluz|YNa-dw)ntffskq*$VVhgmVC5Aa-xIH; zxm?$>@9~a+ZW1d&Lbswxp?$Cv_84dvVJ!Fe1-s%;W*{SiVthad>3)ZMwW^3VL5~|& zKy;O}g3xCl^Pm@IG%LIHp@d4~!Q7H}eNIHuUa~9P)k%?V6X(4Nc>T^8eS=Rh@y7G9 z#Ig}8&<;YWdg@1CCL`2)Bow#d212>b+wwfV-jD0Tb$5+4#zlIRE%$Dr(qG)k_19J5c=cvi{vE}Ut>g&(1 zZan7v>LtfyAqo9mz5zzInKdiv&l03qk-%bom(^sO8dHSzi1VOS8_KhJtMER(nBwm^ zQC{Fd*6I|@)nRH*6i<3x2n8cy)gVP^7*n%k%ZgKvP@KPqM*rV@X{-KkAr$rhK|ii5 z#qNoLbVXZuwQS+x&`56l2PSt9<#3gD%e?ThHPdHFjcetw`ywIL6p4w>Iq?MrZe(Uu z=xN@_+?CtR%WfESjxpQ@*Zj|r8~34&INRMbzRcUpYwwt*U%pglFb1{T%vyg~OZkdP zr}e8hQQy=CG1D?oFh7y-Cx{nTA`V3?N9>PS6s|o=1Tr^AGBNcB|<&8p1fF zFsQf-4m9JuyQcnKq>AMAU!WdxPoe5G_rqgu#g)eQno^+i^A#zowL$8%FA}KIB^hx( zLQzH>K^$=gUfk+&o=GTroHP8GjyNC4;brv3R3nZ#{K?;mtd9v;Hahxe&x}Mz$td+t z7Pn~HN1`XQ}p? zp=bGaIY$R{$ZJ3&)pXnvTquq6m6>8@!qdostU|TY$Z$D-HNLP{I}83s$P0~=(~}_S zH@EjN&tG@-F-twlIwhtU=Je0V%%m7gHz|%;@`RkkisF@VdGXb8ZXfy7c}Uu~AObM; zo3sx(B&tqDq1I^V9Cx#q16LzSKAa#kcge$d#NWw<)#MTUZ1I4~lHa`572u!ZunH$j zzkResE+js(WTTmhc5Ap*A}&vlS;{H9S4_)(bE#mIz{IvowE~%;r|1fQIQLF37vWyn zJ66-T?QfQEsi>tGn~iRq-cNoxRvRDG2Bos0E1597SsUZd##Aj7!|%rIQ%1J`jU3Sf zuY~NMIUL`qjU0MJdR&x^CMG>f(J!o>#}&;%hf`C$kz~U81MD@c@f*wEdW+izj(lP8&O*+hAl$WJ0L-H6Dcf2%tu@; zULIWOB3cIJ`PEQO{Z(GJ;mIiJu8eS3%&$t7U-A4fWU;UM0)Cd_jRpTY@atK69-GTw zz&eQ=i8rt78+$Q7AMt{&E5{Bb-@vnmrqO7;$I7Wb!$qtjRJ|*(f^bx|s#H@n#Pxt` zsyvpk9&sL&Eo4a7pH?)QBC1)+D}AbWtM0lnPDe>^EvxCOOw@7Ix8QIr13#i6enc~! zhW2UYMZKl!Tewg^{P>OPuPG1oXF%0IZQKY8(Uo%JUx_zqZWJg8g1F2>*F(I!c?D@{|BG|H6YdS3Eh%`R4pR~M+ezObIVuzJ#k)fZ+TkR|6- zFU*%UYQyZVXkBdONQ!W73#P4y96qj8eSTJbDr<(*)l@KC^}$dqkda}jm<&sd%dk{V zMzx@L=*Tc-RC6Lve9g$*&d6L(Slx8q3!)<|_2Z7V62cJ{d3;cKw1p|JEUaeSW!kyT zO$)pDvsf5)Oo=9SP73)cZdj7VrS}!_eZ~ecrBxK% zopEa?ob!;cDG`6=*g=~0R84vnlq2X#Zhhm14ED=~J%pOA4B|;9|8N5>3$Kw|as?l- z_7CJd;~Ev?ZND0cu3Q~t>=zJ_EJgw|x1W+USJ?QWRb7sc0}^yXzPfkj z@QekLWw+hJ=ne0=`uKE+Nq1}Z27dUi9u_LFCY|I}2FyfUj`4L+jzRrQ`kohJjz+>x zpK0!9q#eHLO4O6pGu4AdDWzOhjkCrQ@lu5?T1ppl7`k%?4{uL!B$`5@_ZNQQJ4ZEOch*d;JJ@@|SqKLtgB z`G}c_%N-%U9&sL2zw9Pme{3LpktlCf{Y8yA+H3pd3DY^+MTEG)UURgk`q6W=CnAjf z1xh9F_@(;(PIIUww%C#7igO86EfHJOvHHnaeNA=pr@klF)v6953hRpDB4vF*zLXV- zN-R2&7Og(}rx&zNU%x^qPG4vIXa}NVN!M5K%&=n`R8@ks!lR-|xSudHS&`Pvl@NPn z{;=1jocji=?fP9#9^GZJ5t8&`NCJ?2wb4>HGEN!ybXp%>HwFE4-5eT?pri+m`==5Km7DNjUH z#>kYQjsUt~tNrdAYe;LW`j^rjIIfs$7!&Fy@WJT_yiFd0i_MPZku+T0>eu(ZLYz zDs8>3lnXaRrz!0|D>a#>31uUNk`$xgtO&&wz4*s`h$LlA>2nyAHIG1T#Q%$jh7m6i zNM8RpHew}6bIPd>=Cl{JriHlGjCk6$V2SbLnAM)pZO_DZlNOns&O(P$TLiFHzM|rWQ*6 zZ!z*+p-%o zUj1yigHW8F{k0#hEjfnL!8o`Bj z;zW;BFDb`WK%E*iN1l&Zi#V;CDPnAUw>+a1s&fiH!(nCsbFoB9VbWph7tS1uE%3KZ zEuJ!km;xn-scRvY5HDg=l$5_NL_!IeuD={dS;h5LkL&a1PrYT?HKJvgzoGkZ7t1a> zs|7)kvdMB$7y~c@+0MCTnjxvNv-0E)b|{{l=aouF`munIKAioQqD(Sf-CRK^u5Qlt zqgFQ~KU{a~xE45%rKQQoek32klp+d!Ug{aq=bS8R6i5Aa$JJ|!8<6SmJN26L-?HzI zp8f&d;!b40`$LMX^A$d@_=wV3bESRzzW=XVUVGlaEO!8--ZWE+jm-RfE18!zpY`B> z+&;gnJu?e{)*22ZXOzymGx-oVor(3pX0RJAZiD1*lU?kV5UXeJJ12J3A$8P0Clots z#gCa=>mR+?@8py}Iq5n+C)mPu3u=4C6vEob>X9s@2%9mRqDsfECY+CrOCwtij%T*+ zeC(xx*Z*H!O*QX-V}0IbX0*l1p3iWp+N-BH^`?ylC-<^{>EGUF;-U!oN-$kC={fD> zer8sze7W)WV@IM%aa^jh@xtq>&$?&JK6-Ujg(;GsZ3zFO$@m+*_CZ~u^#NUS!X zcweTt-@WNReyGYDdT7_wS-yaAwcJ|O zw6*MU>3eiOOE0m*yXi3585$#_#2l*;x@*H{BH~VY{k``Q02U>E`jB;gL5bA+I*ECMc6`VqL+O{tEtw zks z$saciUjfLw~Wf^-HD02RK3Z*3WJmo8O)*!96=f zstQg67K@rgP+PE!ie#a?C$)kJ*BSLFKZ5NBQ`Z(U_tULQbZaK;rpYEBm^|U$V4z)! z!~_aXWUtc5F&HB&NmunH|F+4oZ?~Xza}%8<7VL-xj1bBy3?~_|!HMFcI25m~2j$C+ zYf51yE0zYDYE>F!YUQ+uXtXGe=C1p&cVZMotAp06N~-0et5QrFeCfpY*l*k0zW)xU z3GSx*@E+VpbWae)vg)JWq^;gSdGSfE^*5^KuJ&sF@fV?*daPz)wQ9T@{+qRCzP*}D zVm0%zn(#GgD#g%%CC-KYMosxzdmYRF5t>qqbb_jmK<{6>Pn{ds&a(1iKQh-SE9%@lz zRY7WkekM*8rEz**8eiRBzSU?wOPfscGm*prPO3QevKx%;Ch4=A4Wlu~!3Bt8`H@Qj$^&r67&NXaSEjVUmhjFifef+&i@kdPJ+ zgZu{Rl(U! z^h@B?^YM8XvzC}pKDN4wj!iRFdK}A#B*CHFV^$Qzp_J@U+Q<71Dj(%vAhYD3kEjNT zOJke7vnCr;XsN@F(1yPoix`Vtk)yXgJRv-#`=O%5UNwC-eFizVhK?Y>qaPuvRFR^@}F6E9Ay&dn@>orn6?TI)EigjZf>r+aBb*E6h!>~RROH2!4-3SFy7VC2$|Cx2iQC4xYk7{N8 zvMsAfozwM4VSNH3V!aY^2o&qaHr9ug0_#qpdWT_sG?owrckvh&WwE{+V@B$(>w#TZ>%sBug$TE7ZGeVLjqJDBe6wz?;Ku za#~OhmS#?2aC4X$dZKuiW_3^$YYwO9V^+Rh4~Mk84spIFC({bI$k*XE`D$S*O*sYm zI*fd?YC1ftZK6<*l+|X*w*$mkTfT~ud=+F)TsFxtFof3Hj>jaSKIjrK;@dO7*%+j)%1L%t6^| zukIP1@S<;Qv`ef}t603VSXh@nm#X)_I-Fh#Gl+^g(~dw~8n)jDJ0-EPlpPBD8>rs} z5c;%JLQ^yWVw&WAh4jE{Z~$uTBstT`i5|B`{}OwGCcx5KnY5Rn18G#xD%vk>vkCVt z+Uyi082Vc3sUl@43`GhRA=F1f{zG0Y8x==c#mOdi8_||fTNcIG=eEscp7_Gdwtd8B zk~hnH$p%cNm=%8|m4JE?OjjCuPN#H!W<{WUx$*D2q)$?%WO%8q?A3hfs(Xf{&UHUq z$x(WnT)v;(i~)|5m5aIjAoER~l@4w!x0Pc7s$%O+@z~9t*zv=0H+`p3r*es2-C3sE zt$N+qvNlOvd~>m1&v}v3AvT_sT_)+`oQ+&|IFjECJ1wW!ey#hQIM(5vp`obqnhH>_ zJ29e?$)_rxG8!VZu2cXmrh)HNw&LC7r6{{(ZGP^^p%OwB>hsX6!=b5Oo;QxxSx z_H@Jk*mWYCEsb*rbKK5L?s$iG@^-{nZ}BCQ6I?@g4L?tK^@-E#;8Bd-zVvqPgNV<8 zU%5RU2;{hIFE{7#R8V*BQg_CaKtK#g`Kq?4*04|~zjmhBanPkkc*1V=@E&=#th2_G z&3HITc%^sO+B}hVB>#p27GLfXE`CW6 zI4tfEciinBt!$p^(!nUTHsNH9x9#A)M#UCvSA^pc2|L}Vv`ApB5_cZq(2sx#+shY@eQx{6 zQsu(2yS9(qHoSf0z+g133G&F4mXWfs9&tWmE#h><$%vXGD1Rm55GW}Lx6hmnRsOaq zW>_hhnsd-@hh?lTfsuluR0d0YHzriML4`RX?l9#Q?vv@pd?^S0j^{&UW7tI0x6KNx z{6NefW-og@n^Ap5=MNils;{Il@^?Jt9EP8nm~R?Xxo(F!pJfaic^+~UM*cl9eJ-Zg zK$R4cao&%+XynSPhX zBN7hjapw>^4%5=C%Z2T44Ni51;i%&HU=woqhX@}JQ{rKnXSGss;!Rd6hbJoy4ljSO zm{k+v#Qqxf#m5m&{`0gj#jqjASA|m~sEP!Z!yEjJ>o`x$qw)FrRr1f^Xh`8xdZlw&u?oAqsi%rlMyFCiDw?vpPah#YJeNympZe# zd|x)7%U_Z$?*pHg6y1w z${dypj3rVEd4=J=9^)0>K9dcUP84nJ0f~}xLdjt$m12n^=&l2*D;?4(Ou1ztl#PTl zN^k3wS;b)ZN>L38tHuGvL#ZDW<+>@dkQDklLA6rKPZWcgodP9?DLH5gdmB5C^QOQn zU?bh$<qf0S;*yV7&LU`|v;ffIB83i{`xwuuFy079MQ1|_ay6;v>C(n8?vz5!a zTU^#1`*62W?br#yDJ{0KHtYt*5!at7>0 z8GAt_I<2aa_>>XrH--%zNlMwpc$Wepb8lxsa&b;{q>{-ixuP&m7MmSFGsxJ5yWI=O zDIAJej@Tcu7_l3a?d}%Bs>1EI+oKc&=bY%uVcBjhky0uU_JCr<)`d_uLfPqUw%er~ zv`#6iL1Ejj;$^#_dxgVs`rBr^S*0MCj4{r^WV^9M85D(OEJl-(fD9oDQ=`LpNry=v zcDW!m@m`GKicq!-YDl#0@@(5x8m+kBq327o-6zU+D|u}9cl9dd+B-DgIA>&QnEs!e@%3*%u`1sWdqAsXDL}&D{s#*3pmw3Y! zi>xw`yy8!_$m$D?V&qtt%ky<*p7Zp1&ei8RU!UikJ+;bsw9w8N`SzdE8bIpCF#DB) zYYON2Pd8JvuibxBG7^@VN2gtG41*lx4>R_rLT(Ct@p}J&(KXyY6NS8;pM~I9Mqzuk zzl&cD=)SO8^kQxX8tVPiMvmSX@d8FD3#*&%*b)!};g&Hz`&!IYC|*GQwLMph zd6~4*Bz0l*ZkPrAQ(>?iv0wS1rSZhHDBBu`7)-};$j_rA{a)qeKQz`!f1;~CtuaiK zZ_(rXSMfX ziC_ifv!}lRgdd1_W5hiX|2*RRg;&z{-8}pxSd*golcYvlo<+HQ@it3G5@tGpZHGK( z)%d!ze}0~SE-moS%e(pK z$|C=~&Mq_%dti=xbWi7NpxFyvY24NMYU2%^zhm6l`6{qDdP}G7Q`UE=;!&nBV7wvh z6(h`&!bnHFN;*BpQ%~)Xdt-`(+;M96JOuwnOQ%Ed>oGVftzdUv8ba(4VQk=D0Btcp z)W_78yAD>Q?e@kRQ=->9fi!*>xUaVw)7m&e)`$93H&7-z4&L!+KAD`GT=#P4Vno=@r-b4DEXaWI>W-l+*+n#AQVV;BpwuN8&>~KjY-^%1qDF zOIg^qrWsTWM4f_M97cqrv4klzJvKr?HL9lFAf4Dvb{u6Dr(P$qOO`ZdQKO_e&(Ht^5w?g1Vfq`1gmUXt8(>? zudke6U4QWWYTv;u7w#R*pI_Z^u;cvdH3th?yOl&0OD`Ik<``Kf7+LEHak7p_Ue2$5 zty)w?o(xh&CgSqLLbwi=#<5Kk!cG)!`(`3h4B~PM;&T|54k;d%QcCjZ|#f|Y#vfi^0KpZ#yS|YEORb4CSdFW zZAVCz5@lUc4?xz_iUKFWcs85Q4sOfG70FP65&I@W@w0<5Ke`L6gw#F^rCT#a7<&A9 z$Js%Mc4|b7km|RLmjYimr0OPOwUvlNB8H%qNrIk$gwXN(Zbn4>JykKgW6H2fV5!c5 z({NZ0Uh(7;Wl=PWqQdHRVCZ!rP7+ict^T!c9cyUq+k~RlZug@oo=Y%6ImG}SfJX(FZbnh+@j zontZ%$8=3xRCxWDb)!V!ut9|0qC!#U)``j@p-E7G+(LD_B~p*KQ2qKr4Li8okG6ve zRdEG|nO5Nmv4s(|m70Qtqs*RFfY^kpk`2umStx^RSWJZ^aXA#R>>{k^jCfrkl5iM% za2R_Sj_E2VdoapA6sEPKU}_JFlYkVLcIESEY11gSy4}8Y;X!^(yYh{)izgwqQ-E;?$M2{>i%6q(Fa`Q$5h>)m%5)!vy#hxf#>e> zp^V_Uql4|I-4ap_MupQ6C&f67%!B&dUOnN$Dyxs`sn2%Rb#1xebkA^ozVdCoA9d~U zotitDg2r2F*q^Du!&A=NfU-kO>VlFdUbewqQj9b#3JL8N17o;gp}M3o#Ea1(g<-G) z>Q7ZFF8TazVallC$ZIOK5Vz7+0pc_PxEU59_#8Ci5J6Q??+B<5%PgUY(-9{lPDHGL zVnUdd_8&f+R@$&@?A)2KgHdHxtBkCSK-i)LTeqSHC>T{D5_N%km<6Y4{;*>>98rv zdKR5T*fzmmRt%J#0%eDx+!HB_XHjqsP$vd)mjtM)=LI7yVcR zNOQSYkBlt{*$Gj&@%6xaYG~tC*;Z}*4?dv@VrGIw_pI;p1$^LrOD%#!sg_ zRA+-qV{Nhf;d;HbWX3wt++p!7EVT-h*$yr zDi=;PpgFvsLi$6myr@Bh!cpbKdYywF?l5!v-HInYr8wj;BcG9eB1Ahzn&k90{bs06 zjU84_^z0lo!(kdc6)8a&S4z0!=!g9 zp7gYQ#$lY#X`&O6_J?@-p6SqojO<9o{5?kcu2B6ThKoYvQvy@@IF9xxZq z%N|_A@z+RlSBf~PA}x-;%Lc{Pm*U$7fbN(wr4)qk9Pr>UmCq<1MNdbHM#8F5?VkhF zfk1H*nc{Fg!|Pk+{t`k_?&tW?a{sjCuDP)qnuV9-Q9o+-g`>!SDAr#A^@sd>Twf~x z(_M+>fB0BcZTYKM@{UiOXgRCCRMqNsmJ%|MOqzOOibEj}a3Fe7mD;r34QdiI0j{B! zGD*@)f5f7TFuyip>OyLC7!Dl9aSg4ii2c@w$ z7nmycy6NZY+v%--JWkj3{urqz`{+Yy>yIMne5|qnMnONxRiuL6Y`PZoJ;yWztzyY< zeWz*YGVIj_%(k~#$NPW?SXD`|Jx1QCfoljjLy`o%d}_ctC{qeZpHUbAI}8U7lYYqg z7_Ws0*eHLdDiLrNOa-hs2}p72THtb8+FbfQ$L;pg@6motht-Fl63sigU=fc6;RPzR z7LZ0^u3uM=-|-qFee zzDy|Y(0s~|?8sz_ndJRvYug|U(5ngevYH)=*g?LN5ho&6R55Zh1IW$cw)NboazNiH zNZw&2zgzK?NGT0PO6eTc;WigF94R>kN)AJ5DpC?};zbngs)UGl1`J~h?u%y&RB6?- z|0~m~zV8rGE zBd#m6Qk#>vLGgl8%jSu9XGZ4UU|jko|MaY|55AzZxGUgilt14Rr{~4JoFPe7s_qts zEUzKsl&cO4QfGne!hFPxNL9~1&03tJx{7SUx4RrXFz~ zltH8>rB`%Pj^o=*YaymAs02Ja2aqtvW0CUyu+-Gt)QF2y0sU)`6d zXu8k)Eke=64*1a~R*{K40e6r*{`15-H8)1^&ZV7;fOAx8)G?lnFREX`4LP3q2Fw%=blqWv4 zcAYb_qX9~ZGe$P;z%`UMM3S0Rj@a)ayE+4MC_`a5a~Mt?rm8{bV<;4&5+f=RJ&9%* zOjV*dajiI1a`3-eRr1$_qDtQGN2}yxQpr;gRr0>?ghq&D^j&G_ENST8yMH4MRj5Pl8H!kTozLFpI#Ufj+7($t zSwC7s%9`{%{()&I&}DOrLTa1plZJ+kdpgAj8X8p)4b2$Y#x?S829$;-jVxlpH8j*u zl3G-Z*zF>Gongdh3ek|maNsZ+D#dh_lZK3FNc5zk0Wj5&;?xSop`qvfXRC%DLnvzK zL_bbZ3gVe6nBj z<_PL_?}H+4inuZ2NfA#J>hw>RhwI#=jH38c^A15ReHP{NrJVj@ajL$c+Lazp{%q9A zAEpxY{iR$y{1bynC7lw47-dv7nwW{K8ZJ`uvx?U#ATgv^5w?2c-+FXE5}uHKDu#iSR%`grj-oQAth6ZD%6XVDc&JAt`YhQCay$f zmW9tT_DW~U+&8{)>}dBR4~ih2v}|O-W@Pj-val0ED`1RA|IWvFs&cARba;bbV0_>c z!6A-0#$Uf?TL;~9(1*_^Xb-O;WJYd}-tSa5ZdN;obdm&t4r2$GcE zdt97dA6Jbq(Ev6Sh%{oS%kAq7_)`?E9%QcBUKL@@qGod~j-&($%->Gk?HO`7f zWAPv};Kkc?l$xxeD4z|Y-{hqK5d^M4>`@gx(xdW}w6jrFau1vN#7QmZd70;naA$H4 zdX`bug>Acp6Hro0q7=019JI?}RFQQf@D4&K8Og7jRZ9mL{4G0iij!)L_j}yK8xd3= zKHT*2CPGQyx8-d7%q{!uO_R9;k zXuvNY@IJIqN2qJYn>weBdpakL?6ewL^%?1apjxAH;TTm}D@6b+ePj9L(mTQ%BUyraqm2WL zC`a!5BNoNI1_*aa##@DF7%lX-PMR+%1`1B0Qisv}fZ|CPB~dUERt@gBV5)h?QFc&i z)w56d4~?|-II&;)xkg&gOXmHUnY|RdywzX7eBd$ReV*DOE^Lp8X1Bw*^A+vurUqGj zA#%Yf%*@3!MI7aMuGr#PvMC*Zo-s0<3Zd_?MzfgS71K?RRvKxg>JiNzFtie1qBzPb zj=Kt9NMo!GPBeCHIJ?=((?cth@BW5uTjm|N+Excu_X4;*wr!s0uf(?LS#7Hs@%}>E z<}my@OnMU2O^>!2X`AX1{X<~dHpfv`akT9tKWlB<7@?@a*ZDD%97o%dv;GWc2EiE- zgaISrX@#T5Yk1oK_!ODpVqOCRnfm4A!2vt_6pLNqlKBH?)$Tc?1jeM4CWYK42v1g>2Lg=O8{umAe z6%$#7uNtes=7e;=jyG3y(wHk?_ytoxAG?jqp!iAbzu@9H+J7GT_$fpIKPYhl+7+*9 z=};H;yl<%wd^G!S>x;!Mh53jWAedBv92+i<^)cwW=vT`|(zD=iZX&G@6-Xz76 z=Rc1A8#dg!snlIFVjk3xnl;k(gcyL4HaWd*FzQeaWaAWM?J%yWD^ePvcXMt4#$ zHDt%ZWslOTn;x_rU6DKK@=qFu{8c}4)PTD>d{u`&9<%<>eAI?U`x@4JCGC`~EXs89 z@W{u9Ev_Ng#j7>rsdqtI4bNkyc!s}GO;*;9o4b{tzi>2Mb+;T1S7jQVGAh?{G+Yf{?P$1&t~?s93NSG-NEZ{t zkW}V*(8_6Q_%*KPc}r!wzw?%dbgeqhYuU&?mXRg65vM4m!u}}p5N6xl@i;vrTb7^( zM$ub!n%X4MoUX>WgT*dMQpHtG>pU;0nia9CXNX&Cp7&RK*O=$M{y)`=@RiXG&Wm5v zHwUJR2R)~Y2R|cN<;#sf)_2&4+@8uFi?@xG2GuX291!ksGO??UQW)P?q*2Mu7wD5U z_H0|ylK*+;M)|xN`S5wSYzmZAOjDonY5BU20`LKZ?t!vPd18f z)Be^fpz|)JD$5td5(+={OS*gc{%p0w6*1W`;^~a^G9$JpZQ)ilWksL<608`-?lfdm zBAl+1qjG(0#-GkJFPE2CdB1mEk;p3`eM8~3?)P3J^Xm6|hs+!~Zml8S?|u8rWjLG$ zQVXyOab6m`t>oLf)nAe;3fMMg*RK?fO1jPzCy~&wsdYOZES{al)c|Cx$MzZtFT`+A z0dp^tNsh_wd@@gr;uMc@$L8L8Zj58M%J~km4Z|Ew&*YodhDem*!fG;{k1Y(y-1}%U zTqE=9G7OoSRJ7I*W%!oGO_rV6M0O0L=JeLW-)k0&3?F_5GL-DnYd1G~5N~f~*D9@? zTdkF~$b*q{Fl!mfk@;3{Tgb=MQ*_6c`H_2H{XUh1W|=RVg+H4|nFShT-^TO9Ph%-s zJ^Q{Sj>kiqG}j5miREYfn6V&Iv1mx1qikRFvPouZaGo_SjN?PP<>N`ez;;b)?cF$%e6?*#0>p7K+tJE@!Z{~=HsL3L=R+VScTD5Fs6GXU` zEo~#an?_b0!Z3@+KQ4W7zwKeSxOHWX2l^+ zx>Q_x5a27{mypueeE&cw?uWnMkJ=B{Z&N&nj_dPvjb8Z7&vH2U-5VsS|6eAai6)+b z8Hr+<5nfo2xLUkSDm7o_Q&NaM&_LP~Hn+Z%cwAUG<4x zpv&Hu^K*L1$eP&5J7e-mh$^QJv7mU2a^A?g+lUPtnGPB0%EIcVcmHc@lx!YtxS9|~ zS>*90;n9YuyfQ@5kRgip3Pbc>f4T&D{tLtBYm%u9aY}W;+a!qTDY%9qPKfeWCS4JS zz*a+axXloUl>*^ zsUf~fhWKb)U&9bTekWUn9Qc%Bq(5Pv+xZbhp~*t~khu&8(Z%*hzRIBfr2E$S!pv$T z>2w@P1G2BJ@LI#^8ktugQ$uFnIJedi2iA-34g;&W*Iet!`NEG+3v;eR%F#0CQsbC& zogsxet=f21HK6umBbEcn0eMeNJT{|}GNqD{S8O0QbPmAQf*`Zw37!Zl11{Ye<(RxB zANp5gHT~&tsRix2sEb#|c6aEJ@PkH);#MePYBfbnMll6shwx~MSR?c5iU^rm-n7;b z6>;Zxf+EDa?APp~km}i6?vCBsGn)JA?$$cB_B}tYoY92$0?E^uDlhd*FkukpsF=Ok z3Mp1M{1__UWh`pK^u;Fmy@7l~(Er3$BukmaU;UK#2%qAgFkki=lz6X7xAaAK5jO8t z`OpbL@E+fOkD-Fpp1AGQWeqb?Fb-#vcl;^KT3Oki@IwU~7B7g!!y1ac8fQJBH1e#u zb>5ZYyX^DC3oUfgrVB!g{3UqTh9@*zbaH4>bFhsYa{GriW){B}r&fV}w=!whZa@04 z+w#^;1LA+EZy(U zMnTPI?$TjoE@@;KGBOMaSF9H2+*25OoQEb8j$Ulpi(5t`EneeFXZLCyW+Iw%KcVG7L?NBxen2=bhYTTDg~J( z#P%8qCowG4#!5UPXvTj^s)R})z!iY(v_oyN7C{Ti897g@;6>7>>g zqK|pyKL;Np?pJsAnax3DwG|>$<5Fkuw5GFPQ2P;=|g$KtBe0A`;#$=EJ&ik}RIiDQYY4 zBO{ho1p6Na^{3}b%kyx{v&y7-23wv*gY8~eVz{D+hA7H-}_26t<3w|ZH($EyB9 zE`J^uSY1>|@Aeq$C#BRiD>C~f?|nbvpX3#9kxY1t;s&xnhN>E>+GRLupu*x(;nvzT z$6#j?b@S~^j%Bv~7XlM#A+N(_AlXssNZ=Q&4?@}GKr2x`16h5sc)cufhAQz|5dmX0RXQ3cDijpW839ANKN(TsgE~_}nP;u$h{T$leTthz6 zQt)B&QY?^b%$%u5JrUAjH4(-;qJ=`|}bC_E3{J5aJB@1c`{}X`bIJcgD;@ z&$6&dVcVOxDaBB=Q)sor*uh*ZVaiZ2LP0f(`T`j2gQcoza3I&nTt!=N^z8*9rxqx# z2K23dQU2MtVi0jscdJRJ?_;Zz$MZWnT$Jm%tBx)-_C~*@!2iaXkO^4TnUIR{rp_Vb zp3bt7u4|+#8_^6X>rpwq{TVBjRlSSHYw%4XUW2df_5{XAj-x6j-+m^fq2AAb@Gv%c z^@hdyx?CdzbvztW2cy0VqKCfo5i=2&^^?WYvn5XFQn>AN(nE?tCr-hb9melvp`f{G zN~vy2?ADnQ?*kM@?}^LPO(PwLHK%p?z>zo*9s4pNcG3m1EVus<_wvDtQ-{iX?1zTO zYV0IT!~4ei+pj!MPL_$Np!W9FPlOohd}MCHOksGDIfeIUK5-E;iL0*KRrD~}GljVD z9F~iK#W`{Z&l7(af*KT;dLTdRj@3W1PYH?gha%QDzLC3C?_STfs@$mBm*t;JvU%=S z&2rP~HCb*{?agwdDji4^5%`ReMZb}K8zZY(BLklhP8E(?ujmo24@E313nYzgy?Ib6 zaOM;Q<}iX9jwM7v#Eno8Wr<)EOnbBAD62SW>4-xDNA1n8v3uu?fJ!(s-F=DrvVUXxDGVlti(TG@_-X_=~)ksQ@a>@dYOcP9jS7OFcgD9yc z)K_-l8B{!;pEHSgeoonSLcztmBDtT;%1C=wUADiQap4VR(Fc|cN?*8my3v{s2*9$6qs6y<0z{*@qC2CJZe|` z_1<7H?ltMQ@q20M;_JQ{`cpXl#A<`lIr;p%wDjNw4F?>O$sq!8HK>}|Inuzg>X-FJ zON{p);L0!S+tMry$Iu|hST#uA2EhsrW^FI1J ze^;ok#G6Vn1xgM>sT3&{h4Mf#+%J^h0JYns;YM*9J{7mJf2*Ixv5vV(`|!E{Gt2__ z;W{(PXFsWdG|m&!Lp$N8M{_%OOO+&g=s&YLnOG3-*nqHM16#Q8l(pPL<0T5+(F}Tm z8PL@;`+jS+G}qwVv_!e18Pp&rB~3@1j5q;G{bqu0=Wv_)cPj_#cZyb&3ZwohBkvzU zQLZ@5SZJh!3lX^y6*;|4{iDi(K%4@*4x_Z0NMSlsPz_RYO^DLwz;w=|I4MnWzmd|u zBc;*nGRZ0b5R~S(*s0ReLjsSN;8Bj0)}kC%YW!eJR6rprrzAWEk|++ipTL<*uT3PvcD6-JbUAbi;V z6(>fFwUD;CzgCG!Bq3kR8f=SC6c zqj)=lcB<%Dy@n%(mHaG?1 z4nv{ClmcZ@Pz|D}8YSK?Fcq)jJUdq!hYx8k-$%a}qPkfs=3w zPQqcF#GK+Oky2vCNtjJ7Bg3l@!5LxQ>20E)QVvA#6xekb3Ja0Ke59Zn)cCp((I;T) zBorsnEAD}b{>2hKpWkPaOTUJ9fZix&)BO`Yr>hq~(_+8p9J{wP^22k&s}p`e_YuDI0Z{Hzu8 zpZGKtV~J~GaqAbwLA3?qV!zHP8M=8;f3k*F!y42E!OF9cW$zv^R2Y$|aIIH0Yh+&i z9YV;=LD|+C!mAn?m>5s~?f@?oR(mfmLvfA^@8vxhQeIGlTHbQ0k?AWNr!`;MWF-fR zSRN=QeBGCZafME&ck-#1@+}LImFnvoKl*jHiOaB|V`M^QWDmv2q(untijQiUhI93U zX+?pP5ho&6KsiSjg&l|6oa3C@PRgiq(l49?OLG|ayIb+}la%6+!z`wZ^i!9k+Zvg> z#q_C|E{^P;V|ZGKACg_N^;q_iC4 z9fqSs@yNqr6zVWFm7FiMPzn)eQhdJEc8+tGqjjP@M!!r-c1HyOSsl z!(V$gzGAWe3n`FZl1aYxSIhxk;!C)XR=(}Wi`#!s_5C*;%YnM3Ajw`1F(bwd3<;Mt zw8L})syaQ_dHyx^t8sd^bqd?WI;z|Vd_gI)RU@|Q5{z!fSOb(?6ZuuMYRQ6O(u5zg zco-RtN7*6k<>!r7-&pVfwVW?XDEiC!6cb3ll!I$S@K4I?K6jGPQGa+q2}XLZw^RNs`< zRA5H>91oAyXP^~irE=m?^im#w`uFgNg)eJPAp7GvNuS{3Q+hjCcC| zBsxHAwXmft|Khz@d_euReU?w3e(t7g!wjLgM7`0EndBF|5^E(h;fpGm3%8M(Z6ou+ zCYiTIGH>45PR1u8;6~>vn%qpak-5G}<_{v7hg{lDCcIH$qRX3=CYg7(k$Gd2%s?d5 zKX4xx8#HAT-d(fWXe0B-ZDcNQlGzr?oB)}Irg6vmelEG=by~(o^`|~M_v7%}x9QiE z3sMSJG!KTENqA0msCaok<$Kd_HORrTQ*=2<`bA~6Cwb)C8Tf8#SGT{m6dN2qZA#|r zGp*|0M<}ZM1AfdT_sr_oZkpWdolEk;tHUubo06KuR!(xm4>o9T!v6T$J68M#ihiGc znP)b%1Q8=&Vu4PlC8}$ zMdjx{7-dLv<&%ZHiFVxH;D1LTkS~q*>L=xU^#k($M+suYv{Uw`%?IQ^>NIDEGj=xs`iE7SuY{W^CS*_R&cKuiG1tW+xaBM z(phI_o6#871?=U#_0y_{`nyF0O1+U%oTAEUX<|_^dg5^j^ATz-OH5@w@k{YIQ2p^! z*1h_{QF4FFhZlOtfm`dOZl?RIq6ZaRO%DOMMXF5=+pT=Mei{;r!9Ggz8i+TrOec0-;g>))J9j)| zs^bx9&FQQZV!3W1;xB;u<1_y=^qAhk7xo*qGFGm9%vBoa+&9n3`7hBm0}R2+Ks8wq z521YG>JYs_OUq>t0sREv=&m4h^(88&YRRuEm2V;Bs{>XFcs<_`E0%pA#bPmYF*P8s z;}yR44oTwnw8_zIC#lHuVX_D(BTlFYiWXlY>2QnnVvb6|E}R0R4r`GdONf#v7@;t& zFcO*p(;2bjD66>HXLYczqc<m;vwXV}LPe<{oEB%%VfiV$rDr1KC?H9|AWN~W?Q zP=8W~;*vk#8r0zosAEtWX@xS1{(zBLp^;grkSz#d@H#ra_m3iZq;h)5gc6G?15E`{ zj)H3`N0K!yoRmrtR+rW=9PE-q4yMNZ7i^AMBY)pIX1$tF9J9{%BllU~s9#%QVH3%_ znrH)>aEW}hfdZ7J`@CvGXx*Ux6t|43gmqA9nV4piR(>C(W6B+}=n#mDY%cY{P?xfa zT=-rjRG~bsGWObqX*i|fvUG_pVCWKR!6BEQa0A9h?J~$Eaj4WnWNaR6Od1pyzc5z3 zCkR#q1^&jg%%q z{mJYUm-Jp5%+4946(Z@?%4e%Ka_|JifZhV&1q2K{YZ%*L$U6i`Y6@a(3XZPf31~`1 zOp$v&N>9?fe`7nB^CT!b&WF1QMPu9K$4v4M?+?b7D#z8Va5(O*W|rOxreX_%?N`C2xVC>jWN07SSVCE?B(arEF3!o7#OS_L2t zh||C!KbBeIww+BMrpBfKu4}UL`+=3@lnXWKJ;GZTLzN{H#W8^BNWnFA5Oai9 z=4_=f*X{~>+L$}N$=uF@XMP3ZlpW>Dy)B`q_J<-DkEQQ|MF%V5 z1{{Y~OLr+XYBg0aQz$S+>6FN0RRHf{_F|e({U;aZAyK^sPczQpEsBI%e?S3AQU%c0 zM@T?lar?14JxO%W|@I+uuYx@r~AxCKLyutRLM}+gZnJwOU6y zl$BMC3K`CajZF1~(3U^~?a0Z7(3#{T<46=H6s$T#udH%$R^exU-0lG$#!&y%NsK8s zMe9O)Vwf`_ud}KUuh&ZaAl`u=aP-0p-g0T2qZri(b_U~xa4WjyxIhiB=rtIMd$*bj z)#`MRERCHqr!>dKmg#vhYf`BJatD+P?X?J#(UBnERG$4xL|`Q_oq_2&m6e~_wo$&^ z__t)Msqw&%QycsRo{K7tcee#HEGeAa%}Yd{Q%uGs+jqgfsBp??PdQVE_ zI=PKf^&NG?dlSQ_mVv(Nb%z9)evc}q19lsE4{XG-7};MHLOSHD+h?(bt7vVHDtjCc zZhaT4nX_u!#%lYYZsG@$Viw6RL)6`+0 zpptY@$31uHq-kviJ$>=7Ej1Bx2BuJK7hx5VWWk8~g>(SDE7adoHk4obeQ~2$EkKkj z_pZ>E3zc9p@U9Hkug`h_Q(ok$sAsOD+$z{^;p zriV*kg}9FF4Z%M8s+CFj^^e2%^W*SC`3t#K^1|vs!G2RR9XT+({n}cMT+uUUWaC@g zr&J2hlSOhiw*3-v=y-daqPxo#?xT~r3vx|mUBsbKb9_KAzX*#D$0IKpuR5tZtJ2kX zH7}^gusD+%vLMSFmsOlb9>w|6d*XFN(X`9Z5sY*MV_1Ak0W%KQ!=74*RtH!1w1eM8 z2M<55`WghKSFDT2a9)9hhsHOJ^C)NK@3FIAIcm}yJGHUKGE>{K?7nrBs;6{nt1GYZ zlTdHiV`XQMO%50FxWv{Nfy|w~ov+CxkN7bjA2Mvxc{h@+IJ8fQ@di_PP30PLUHvp! z?;r`UQ9_Xf?78>Ibj@dQ&iqZtS=ZNrBb zqw+FnuLMmYT&tXtDLp4=Mxv$=wMg_%Y1AT7rzMPSNu@7A#T)Jw9Tnvi1)jNs5MPk- zUbO%3ckTV0oMh56T*3R%{|#k+XYXe}d#}Cr+H0@9_RCYL;&!g#YDT2PsTuz$;aA-u ziFlL~K?o)`Tk+yA-g~ckZp&XH1(tWEyq()8qK5ESk#vgM1C?Y(VWp|zzl(c#v z{Or4s;nIKbb?Rz7MoapKdseP)xgVkMfN_zyD#xnbXY+!k`_(?vYMp5vy8Bc1!`733 zLd^t^gXNedl%u&-7Y{pZ6Ock>co9UmBg5q&jFKk&!T+U`?0kNWtRiJS#qZqg8cJ{_ z_#->znas;c7D;08gOB}ohot81VXLKwwdU^?GiGsB@(XwT>Hn{N{?FQ{@3FKGXCuq@ zdE8B3wtY?=z5n((&hxT;(tJU|*G-RDvE-qz9+9LsCm}}sCb~P)r2U63AERT4^|W4f z-n0|#t_hR0btDoE8tG&Zp`Y;zShI`wJz=5*Lo#R zl~*#zym8P$AaVJ+N@Ui2IsOhij+JAY$lRAQ_BQX?*L=}nUq8C9ub)MN5iY^)=by9g z-PkIDUeKtkcxP|tX7u#4Q+y)~x%4D{UUi2&%-XqYeG2)nnF)MN%$1R0;0BYaZMD{_ zp@&%8YE7Z~W`@UF)mZ3AH7O@?r3$cJ4)98U?SW8kXl8cL13IJgpXrB7xmJ5s5)-w% zQ3YxEGDNGP7}CgNb1Xr2sURX1%0i{_IONWB4s@m~2g6f5E7NUur7O&bbZrqnCCnSQ z$>(@}z9CY^I#}kj#_uSP_x$Z3?Q_eQt>zoz!t=;SOOE)`oJp44Q?@L*cqcShsl>7>Ecp)aj3(w2qIQ`A?ylxJes`w2* zECm94PO|o#%g!l$LeYruczTH+Cw_uyAO_K6%MwqcSnb0tPz(>1gb~aT%$b;HpjHS{q@%F#_=X-A({(#;hVw3CPM(y!f++}S_N4oq4?fnKZE~YDk#~x45`v9A_dy3b8;h*ni zPVt1TWXGQ5Lr>BbFHR31aPr)hvudu`=SBVP!h>n!7+B>k!^0T|!P?N>vF*LD*^8foeN?8nXsOWn-5F`w?WGu2pWz=I=>j-8)0Y!YfR8iMY`)^rn5U6;40wflCQ4u9MQ-TX>? zSGAThFTZegD^4{^Dcvb4I-&|HZ*#0!Ah?ooDdRk7^N7b@Kw|DsH&6D95uhj^%q>(F zfa=`Q%C&*FRNevl>8y8xPN)|HnK}kuS6K#Yt?Qz^@)KQSw77JPwT{F=kg?`cn?lB# ztH%RYHh@}laXmtdYXS4N+gP(km?*poh)!ztMc`wkIzmb2N4O_d@H(EWjt46#a z?(KG)5DMlrk(Thp%55TTQSzz|t>RS1$&BOR2j-Z1$T$NrQ5sndwb%GIUP8pp1TOk` z?oegFuu!=Kkc*cF-cq>?bV5E6i2pN?b@;&RqGY)^6Je&lfm}ugN?A4IaK^!m{h+d2 z3&dpzD!Y3kd|mpsvc8)Lr=-W-sq=IzA~{5^M99bN|dOJIHy?5ot4H$_a*^bK>z(ao>DmyKeC5Xc7*T zyuvbu(8;H)yIS+| z%1j#R(m*#C<6L#_R>!%~xvL%L##)EXbF6i(MUAC#Zmf0dQiQHo(T%moHm|lk00+EP7<{F?w+%rJcgdfO8JzOLl zsyq##wq|aj{TX{eTXqoiPvsbCh7Lu>eM1Y0K!(9d(>8YwgQ{6`DK4k2l*~HWl=ERF zJ2e1_a({~=iSIQ#nom-6$Pa)Dl%zP_K4p69xd~d|A`l0DAk)4;%oR|9TMt}F`(2#g zMVM|OQeH~eMpM)jJfju7HL+{)JQbVvl3PS*la$7dip8tH#U-UupfB9^l}MEX=jhyFhtbiwYtRT7|g#emzo zX)PjeT@Sc^BjCNq0c5N#G?yb~bJ_P8%R>yCt4k4D2|}8SHqBUbQSlvZ%qz^JjYf!e zqpm?$ZLAyw%C8)5RW5ZsP6S3JS0sb3wvcXvN_M%A{J>}s9p^03|RMud4q3qe-L@EUcI^r^Mhi+W3EEBV+f%uk#GS;qbP zw|6h&e(9$>%eeQ%&#*8|T8w;`G7;I*xbgBrTEm5QGgV+3pz=L0He*UrYp(YDw7Jq7 zFdw8g8>hNhHM_b-h;b)2SoaN^dh8>~O;|#*tTYNfkSeEq^ ze|tUr!J?TkN5~wHoHIG>4Vk@tLU+?dZjP#t8GZtZpX>x?Z)bL^muhxq@)=j)cNx`V zWeWV?c%U9pYd-ug7wpr^)T|aoCQppYksvh`*ci=OrSv4x<3KXa938QN;k*yJQag90xyXT-vUASACLqVuQt|dEH%=YLIq~9~N4BCZTfndyzQ}l|F)6Cx1vBj7LR< z!wpF=!tcYoCs%{+T;?-FPn9hx&9tO|kLg@m)1S4L4xGc&CCwX@gNy528$m+vx=Olr zb;~kbK=3czA>j;hB$jcwUV??wkcJax$baw<1V^nG9CWO;M+}LKqy^cPmHUx4j3Ta1 zNhL!sGY*DQ*YqIqbU65shVCw&M($laogt^-P4IMP8&CI?#s0%5$|B$?=O%Ez7aMWN zd=#&6=IPAIRe8k1*O92hSo2kt-Vr#wMhPQxhbngjE>!LeJX*O6yckB`4eo=B4jKCx z{T&tdVKp2F_k!&Rrxn+It<-z=QHUx>D=1k9wfV7M>Wp_++ zikwU*d#0qg#IFJ6wZpIrqK;MJQE}5>+I+D$+ZYy7f$|ibzM1GCSEzY0aQ-j10pBa3x_I404lo#XgelM3jp4{ycY3k+ra4- za`C%N(eZLT-M%zgcC*7DPu+|urbCyHiq~I}ceA{O# zE$7lS{3;FKgstj<+4fm!1;P)OPCq=d_06&z+cjV4%=RGMT?z`)UUAjhe1w^7fl9}Uh;$tIH=Mz>so(Ccw7Ym0fqX7IibEP9;+T*Qsn5iF*wy<5K~zrJV`!|GBaZCP~~i4p>htO zGJ81|oc2vP4j7LlI89%v5T72uq4`Aht8w%?z)BGBycF_KCt! z#Ye`r1Gx?+Fm1un0$L~Rq}Aa;20L89U`G!a>|jEoH9*T@2_j-3Vu33Nc~?4o_!^#> zhE=$+*O~Gx{_TRqaTlt zwO&=3VA_}3VO1`THCJo}T)6EBEv7b@x9M=<+5)(6QvK2 zZB0K6Zp_|spj0C;tSi(to@`q$Dr`cmBUpv7%P*EaQlr=hFzT}sQ;f69kEXs4nbU`{ z3eEnhaC}XLGr6U37P3upG}|P%eAR4{#N=%0qfHWbHp?M_1ZmabompZS)qPz=pKF7EsWcR;6(;;0~( z4BOuInX~>=$4UITb9it#_p(`U;6gM_DcO0wKaI15$s$gcrU;+bV%P8uW`a=GY`_T< zk;Q{+XMMDG;f4dlQsl+J5m}Vs*zS=dA+a68kvu$-ykQtth-a}z9D6MH)8cDH{PVp$ z-SP`fYLtmNo~a3UVZ))aO;ow)jJ-=ZhZh5MI>eK+F<2ItN`r~O0w zjr|Qm%=CoKzMo5k{8YTuck`i$p1tR+FaD|aM7-aNO7&NB{UYi7rj4hrL&zdAlDq`+ zpCm^VBpY?+B>(pZLR_LMrY`T~?Hpb!&hERJ4ODXnAKZS`enx@^-vgja-$5E`a;*i~ zxS+KAsx8>X-8xYvcN_fXKS?!O?izLGq+S52iM!SK+Bz>o>05wjC1TPpg~t#ng+%(S z+`3?6A0S+kGsSQIlN_TZ$EY(WXYm2ikk;DH6%zF$WqZ%IpKyEoL2^YJX7Bm#2is~B z762WSzLUz1D9SeK%**};-p;Ie04;GDFC$X{slJa``5^T-qPzZ8!e#wy{N_LFZ?yF{ z>da|S-YC@iKd`L-Pi)n{@AmdXy6S)XJr8>Dx?yQ5`7vyP@MDj8Bw8X0T8uh#TGEGS z?ReD(!jGr$GNc7`^TUX4ehd&U`7y|E{*xa@%MYW@oQ4K3Z&m-?+uM)WR{e);tA2+a zl2o#z*YaRTKZqugl!$^9qt2KVy-sRVM|Sn+9{nxzWM~eHB3o(#RcWap9}_wRpeBXR zKAG4B6FNU;?n(cjcn+tLENI%1mWf00-E5gv9mqx2tT)NR?L%VuakX7@@}0Vgp>&4H z?;5|WbGO7$>!kTk!lRg>+7qMLp$>U25;*!eY2Ju%-e`?|*^cm$WG}XE3<6w@ITh*4 z&CV(8;)nz~-75m~`wYRyO^>}9`Gb}>ia}}l?gt2Z5H{)4?hF$rkuSf4Va?aES?+bi zW6dAniwbWZ9BaM-m(5H2$C@wg9c{em+|kC~2aPsH&Z#!ue0G!LH7}~-4l3ZEmQdc7 z(Y-%syZxgMz~EU9I*+D3Xl23hbj5f?Cq3V>?1_|ZsDVtw@AdLGcIy(qnPCp`sCe=p zYSl8FT2^jTq{QT9bTZ{S=EcE3jAwag+m2Ts$bjl&28py2rSIUoBY9>QtuM9NSey6f zg?PJ3BGxlbWt_}d%{ZKKFk?Sx<;Fq(G|G)DZu)|AoN`_7Xf&05jAzK#`GE5GC{X!( zbl{=NW5Af-1Twz?mA|0~GrtLBeiL{kEvg}RwMdu9P~IFjl(+dnoce({T1gRr_O*s(nm2 zRQV%-nwdEVC@0=nt`tmiThX%!aWKwLTs(}x9`DGDwd$L z==;gE!mw^Rl(z<+c(fPNq7au!QL3@Nga1OPl>hqbBnesTI1!OJGDM~%sKp!8^$@M+ z(d)})Je?dk{Q?=_tb9x6kG8xuS~T@FIt-$yXcO&{TJQIbnT+M zn66!PH`BF?)|swdw83=kqT@``E^0GLyQmmzU4GGKjekzL2&&@AJ!nNXT$rpxaXyFZ zpeu)~pdx8H+if`zegv{0AIR7dnCvy%=^Dbe)7wDW>1yFn>`=67ge*+9h~uE$a0>L# z_K7q@$;spTum##a5^}nK!zMD9V(fpoKKychOza9EFNL@P))3NAxQKp~NBo3&kxn|~ z%K=b-g>)#CHRI`T)d*h~7Kc-;;RvUd68#E;k?v?-QDLz;!aMdJQ(j8fE*FP;Nao6l z$-5VaQ?Y4rcqpgYlhVZE@bRB_A+Q`jlA2c5HoC=&;*`kP-D34o0H=Zt_?839?UPeX zjB7|@slr`)8>ciGl5%|LGk(16P=oZM#}djXP5M}@k^J^dlWi{98qWtKn!-01f=7lrx83oxK(&MSbYAr(og=r8CJtx z6|>=IUu7frC70C|eYM#usa(17d3)(0th_|ky2{N9!Ql!x@P=+z!1of$u7Ib+&mFFS zV-LX_=)*;s2usT*spwgZ-w-LckkDt$ST`qkLp z(3n3+&S`gp8(b}53>D5A#?^Ixe|2}ard>a6@>Q`~QE@Hf3aB=Zx}we75#}o>f%JD! zg=c@$k-8R`G>rzv`w6SvZwAroHwlL-M*%vunHMiV=?CBX4(bBlYF66Gr*kPS=7w)- zW|)zdyBVvoR|8YQni)ZeiYVeUp{-1KD^(GZlsVR+q_c<=O4sd%OL zHKQ>mYloBdeXL5t;!PjYW*$ZGS=uF{OQ&tEhnOJ>$@P@b$`%eazpDd=FCEHJs&VwsGvVrF`| zz8c6O)IbI8l3Bd}~r&;O*e)LNuIm|Z~Gp$SXYjPI}jzcXPc-N6E? zJWSZC4`%Go*aOxMm#cSqFrNCAIc*}*-hWcf@rHNWIp$** z+u8t=%`hl1AJa>z=I0&|`Jq-R&UcNoA(F;qUtp9`PW`EMgt2hqJ$4XBxLux;Hd=jg zTuj${-S$b(Nk4{&;=AugJsf6!N1$A>{qx(pR*BFX>1lD)@t$^xi$s=hA?cuqcs?al}C^GT5!RdaYsMAOZH$Z5Q5oXj>Zx&rY|a+5YK z*BMLASI*tondJNBEvqS7O{W4ZHQish#eR^8;*#G@nr@Lmsp;E*LA(laKAs;Fw7dySj{*LYBwq1 zc0AL`v+2M12H7>mNGUd!nnkOrf-@MJ@S>-`#@nnAx-2cuB4u*%=`}b+syZXP^`u6gfy5Fl^_`9{fvDe zI%&OUf%JKwgBw^#G?iwO)Dx8!Hhb)ecs^-gI9Q9a-ZWfmy=9N2jXRO7C65(}iDN&I z8E{y=;F^f|!Gx)_XQn58I8o0RjKixJ=7{sM#WVe!8bwbSJ6XH;b}Ey0bCXqglpsOh zQWs@W##$=uBp1lgG!NOOuVP#xZn`y3zrn(b({uB;ZftHQR3XjstN5zO!F- zn?I|;7^*F#yfR`ggdotf15s|Eqw#Wt^LiIbYt=A4-`7wC@fx}?UZbs7;xTj2c#XLs zrJ&-j)Uc$msX)efv2|lS)>B_6dF$#m{+&DQ;KdOi2QQBJBp=wd$p`kXbs}r6cWnS; zt(%Sq+}aMfz6f}4sF>D!PZ7$4*Eof#l&Tp$=Vo{iwD1W~E7El8XAcglpG~iR-UF(i zeNXk`xj9!#-LRo!=}CbHD^COoRsa}h?!|xNW>e(uX*F211VuGmDLyz>B~X0JZzXlG z>Jub}2OYB@9axvkzPkwC#*B#jT=B~2qq_U-^u!eK-Aw0F*^F#b;>Z@1F|tXiBb$_K zheu6N+fg$xYD#NH%_%N8!N?|OG86m#3DbHqGHyh;4a&3uvxaG=lWDzROj06@$sg&k z%mOjBz?h_f4r-qU;pwLY<`V!sG;{ZhCS;I^Fel2$)cv9swu=cq&03X1KVOdv?)}^F z2yG^VHlz_;%eazp36xRC1M#{9;)MZalUsee5CEgh z+|fTFy*0x4IRaVa2X|rAuhRBUVd~X%THmER4h5D=?40-}tW{Rf*KJ zH@{&{VO8a@C4^N4k~H2mF03jM!7L(=bh=b8%5lQliW`A=kwj`^x~{pE(cwiBvlrcF(n>kQx;!a+u0UKpk!77058usEmC%Yk|7g`U)q3$e{OnQ=U0 z6}0cq1f~g!O7Z|<`@ZRA@kHQ#ZK}T|Hr1;GE+!p1U=*PF%^gpU(DCHlLx2t4I5UEi z7;JRiY&e$w=C_h2>@yyfYkcLi*Tl2rzjKb0dj6~a%?_^&TNxU7Fk^ql9#Bs13A`ma zGvMS9VL54fIXMuRRh!)| ze5yC@vgPo~EtNIcT^ev7`BjJatXL89==M2HvGiA5u<-KyEx@H{q@&3ky6Ubqg2$@- zQ=Io}_irI=Srt`iOYi(~6q~lOoo-ch@3-pquR5e^T4RS#W45DP>kuFY+1T>f>bh`= zITf0LEoPw^(r~awYKDX4dAR9LLJS8QZx_!UPM(Kr*9mdgjm8^y-5ih5P3?eNi-7Aj z-u7)X-rAI*AZ%n@%eWF{-xzj;0%1DskK=(Xx`7JBdW6g2l|?$ju^Jf2A|9wf+yo*C zM*$?k+;5AIZXWo%u;9K!E36K6T5vv1y#8U%i^lxeCs*Ae6FU|hHx89dOQzd5Mpk_X zZwk|mWeV+OvYJc9qL(B>y(CfUqxXaABZ<~-v+@oMR!V(jCF2s-K=$?pveysHcH5Fa z;op45dd4YGzAXnLM}Y_{DBnzPM`{G(3<<@#yaQbN>j zgwdyg=uvQ&j&#c>lHKNIJqp{u0fQJFvttN7&eL$&1STV97cTwdUnZk%Tu(jy5sNLlE#^Hh)?oi2 zsJV7Z+-0sw{ z-9RBrD;KwK=sVsQr0rAfEq6wpnsZckp`XyMMKOOq;QBC19&=5)!gP%{Ljy2 zlOc3ne4@(k9VO8JRxJsQZprq?^+5U|C`C&d=QGwp%`6*4LoLGF47G+SvSFg(V^~9N z(Rki4r!e($*b=PkGRS}Nu5n>BiEwo$a3S2F8fpg!Yp6{I9_;KKGabArm-{9NYN(m~ zbVF_Y*VBGzRxGGYG8dlHF*n|9R*=8-Z0kCPSHv@Vvvx!h$yZgXTc?h`HYcI8`m0(Q zT4p%oV8(vXj$I36t{=!;A5=0;FE57zlL??5F+f}ido)PLew4rsQvi);=BmGh z$@}Ss3p3S@u&~pGuy_~k$({IIrO51%-=!UK^)EZf*w>?@=cmOh{~QbAN3k%`xdq? zg&M8gz@BHThvSSCBfYiO-hLfu>%OyAW1rY6kCEZg%8FMQ`)tT(gGR~Tc-CJ&!!d^U zvE~2A^pcJ7$v{$Xnz|@cw<9(5v`tl*>bR*kr14bk*`t-?h-w^RN!OLW=ftNi2!3vA z^fY{J!`J518@#*dgGu8i%nBdlwrl3&mHE!)q)*S+%wKcj>d4sl$&>8Not$dsR5hpA zwl_?w@mcp5J)3Q8n`zj!Q^=nY@NiyMFYVlX30+aCz^*@X6nbE99@? z2;?>F0eTPk9=sv>3VH6?qLGNZp%TXsyU(+q!Tp#E|IEEzvb}iWuOOAPTz$ELSnoXCX;0m3YJSSa-g16PjiH6Q)C?tj@ z(MEgV&oj74^%*_WPcT`Oy9ZL~ek+nye9S&K%O=6_s!x1~&T&w3W50q|eXj63yLaP& zt5roUEEDa*Yhd#`D__I)I=i)MuO3K`_1NsB_VFSyPCZSbqH9F>x=K9|kG-2L*mgEr zIZ$nI<9xL-U3pDof92I|BYPFGlkk4>-gTHyy58C1hx@MSLmq4r+Vbo>*RL7xxwnl1 zkqKPM%#8`P!&lOgX77ge=>SCSs=L&)#g~}f;2lM<6LV^_tE$NJ%DtW!bRM)CKFGI0 z$IopA@V@nMAeFhbXJ4gWFSD82$|lZ^m%BlZ=9iN8bOcSzsZUr|b9Fpm#jN;DM^f4i z3Gd{0cU@Zi+g4$Oo+@`7`FH+e(%&I<1M#T%<&UAfKhN&_?)ey^BL#}ikRm^<;z0D& z4M;6<7zB|Z*SG7og7f0UwXZYpNGmv7HyWf_{q%WpRJ)TVSk~2xxNMc~7QLf7Y44@q ztbM^BgDkL&(9oJOP%~&l54NJA3DT~#>N7L8Ccy}o8jt zmiU%mKlyy_d+F)$Y`U(abXk2go<`PbSUILukOr$?p$ocd^4>B{%Pj>@n=PI*{q=G0 z0Y9Icw-oU6CDRW&`KXhRJo#`qZAG@v>0y^uf@ig2Z(86vJ)v`l;48NcTwWO;X?q^uwnj3pm20kS-`x^${Q6Fn~e(&}h~{Vwxvt?R0O-cZUH6}6GM zcgnAkx%bO2b|}d&b|{&W9ZK?x9ZK?RU+djV06UZvICdy0Z|qP~+;UG@lS25kl5r{H zJZSOjfobY4UsfZWj%3+0mLr^Y`IQrJSBly`_X0HErZP@u90wJf2B_jNo%7$B!24g3P;4$Eot~LX0}t-DX&bSz6kP~WDq<5AUkrj4$yEmRH#|JCwxCavnufudXzv4~cTbx6Jf z$u5%VZ(%qnL(iFhd{^kX)68Z%&t7uHqfhqiPJF3rwrh;y@&6c}p=|qq6BiU# zj{UJp;o@+{!HoUj*VYd;l>Jac*$*|8{ZK>q>4&;@J?{zhEGpd64T8l3N7LBnio{Zn z%)DJIZfY-I=OWRJ_3O#?+T^+6HmEv19_jNT%ERShN(LC?w|PVhV<^EVniPi3;gs>1 zisoD+hd{!Xs7JIH^W@0SdhVK^mka31gKye@-7#e$PLJZ`ovrsCuLf2N=B84d8ojKSFH#UnZCt1jcI`?ZkF zLe3r#c@=|=wywpGNq)rCZlczjAg8H8*lHPb>BP=lI=|!T`xqML((xWeWP(&W$)(Ii zE3?-iusFM*#mA-+(7j*{&>B@U4rd(9*bh2{)j)?Z)7f?Q1eQy|U(RKF%l)`)DZE>5 zm8u>x_s=y23Fv$%`pb4B{jP1^EZ%r+I@O(elJf$%i#uGMWCDe~2+aG|ktKy)0k(Lp zxzv4FXfS?DprM^M zGGkz2+_%v+1Q0)-C`f7X4NiUy9cfj{QYeQrI&K(t9Z9pY7I-w)?ldd+M40u1z_hi; z2J$@{XaLl#d`uwg2VjHs1A+AefJA04&(t9{gPEzG%v?<)QEDCwwX%dmirccWKaN;Ggrabog-lz4o^gleiSmcHV3M9v+jqK&D3kG8M?w zIi%w|oI)ZTzuUyz$Bp)BNG=y*l50+K>%8JLh1pCwZnqK2K|N+O4!{i(luonL@3LQR zxlZr@PP|=DEtwSN2r-+Hv*ricPbsSyBH^^(^3a8YTZ8|qI~48h=Le8?3(s!dD=XD% zW0Tb~PE|}NU#E*`FLXqzldKY--RAjQiXVRupHgO7%}v?wHV3FN$u=Ll{@J$@+3}oK z<)F#V=&EI_L?5Jb+m~%q4Ld9x*XcM}^6!d{)A8Fa8K>icIhj`0r})#4KxID6G2iXq z?m1gV4aS|85ep~lV zUgh*Rxn1Fn2^r;%0`3~S33Pb7IgooPK!>;0K(@evj+N^XW{X@PTjm0f1YHhy1!3u1 z$~d2~o^cA)0ACI~lD?S+D;6V+M1$HwjR;?t_l_>aSHScky@_~Tk8cEZtz7{UJq@5C z%w1#U0O+5;C%b-6l)quXDbu#<8Z|1H@d)PVyCd2=X`cFz#m}N(O<|@AUwZLSdZXEh z4A7TRX2+4YMCqCdsc94U6-i${Bau892P%|;s^wyj?+L`B3&g?`DWVqPcIxLT+OBT$ zQdE~aqI!XJA*#1tqo^inD4ip>P4KYz?GGmp_GbyGwYzIjJpC#ow+mB^JAoemQE5!f zmPd(K)dA_V0r>prKxa@=mUrWg3GYMU4MrWgXeyAcxq)oW4P>q(QdUaW;f2xi!sI0{ zHaficuFQ+yd>d+yFP`q;27=L3T+?#_?a4iK$W3sQi+S0Q~AXiK4{5)Tg5_ zqigISn~c6uzh!@i2fXx zZ`xuLwE2l|Qq%6*u$SXM?j-wPiVsY$LQ6R}`MxVPxw0}YdvZ>5EPBe`57_)=XGpS9 zoc~jnSVm@%qKACR!61`mb>+1ZE1751J4|>Vy~DYNnqoLGO&m1`1|z&RB}?f#y<>pK z_KqPjyQm)axN;@!qTD-vjtw>^{*)xUb=gxv)ERxupg)L7vnVT10k^e*F+2&A`&#cQ;`gg66MI{~v>A_YJ_Y0v+IfJTXd~lV z#uf1NMK#WHMLfIp0vTBY@iPYEmH{2<-VfS|ZU*T*Hwlc}0McaU7CN7?4m#*9f&S@w ziZny{Q*1-&%#GVw+os+1^pUv~{~MNWIdQY=DXkJKzfls+rRU8aCIz;@BB&0M0;@e^ zJi1~k!r7BY^G`-Nd-|x!c%8ZpkM^IDSbE%I-iQ zvKp9t#mtbsPm(&)2emYf z)Y~*eB2Cj1)U%V?{AnU(+U3k7=h=%+)7XJjFLnuQZx2zutOb?}{H4dQT{g`sk8K*0 zcQ;KcHZ@HTSiAircd#9h~m%&<4b3X?}VpwUOMdk&iATx-zCCI z>pu4+*cc_iR{3StiJ;_{wZ4ghq}#T%eD?71UefZ(Bb7Irc720JoiBxMrb|;()qQ6J zWSTQ(6&5P_aMHCla6^j9a7?1}9m_{p?Z^A~wO+!O zn3oM^Yb(xZw8?#^a<)xYE!8#wL_3G^ys;sYLm**GR1+5< z|HZq;`L1zxxaGiw(3Q6AB4In&d?5Cd2vbehwX|fk9n0k9ka#&m;;|=EPVrYi(K&`d zj=`d28FUSWGyj^QaH?x4Jg{RZypTDVRDqKj$1_&J(+7kjX=w)?YkHY75x5YFUhj8} zu;bwKLC3-8fsI{14n(1YJhbqBQ0@>2wkU-x``;l9NheuQtV3}t%SyeC~78P_tdm|rvbHC!+>ol~LXfyobI zv#v)t`AHl@R|&^a!NBBK(O;pTMe_}4qQL;g)S=72Fx|R-FLwQ&FMlIcGP=um(cqD? z7}0lkd>3WZT>Zfa9bM>}u5@=rhkM5(;F4K-kQEQI=$J>zvQhsXZ3!(HI3JAHD%{@ z=Au&{lvXSfX@x_Xz3uEPtX<%P*h zUi^NC7vG(E@xUwa);yt8oh?cIg%fE;pK6t*Fh90&Q)6DGV%^(nwgY}fow%q5YyE3Z1TW1s(XSdEgEp+1Xp(eL?8&ujGK9d9`((LB?8UNug z=lXfU4YvJujaNMOatSIUbFp-{vu;s}u$7e}1g3QTMEGCX)f~9(YF`uA&*B^I$My3S z=*F+2>*wpfq4ZCcy7BL)Y>FfXx_+eV|G9q9pqRWXbP-M7wZJoW{q)f8|8ZSEfA{|6 zU$FaOIADZoB$q4g`(H!f$3Sot+3w0o z+Xpp*+Yx48e}wY!o}9d(8xL`vddZ7Okn#p)EWlHtTI) zYp!ax)S4?B0rTqt`J@qx#x0_Xy}VX3p%2$^QsDaze(@nYpG|ZY-~YVpS(gi8Gn7ykxx2Ty5H=_^m2hcib^C{iyN^b0LzvuD z>xyb)aV_HtXd{cqE+uhs)^(|oeN+26x`m37Zb1!AIQJiZr%Gl|==);ZvK z?Fzs^AiY=QJ<}x*?O10d5{{HiLLv!gQjl^+ zmQ2d}j91T+nX$TIS8T4V2h6VpbnKck%L7rtM%$*ay0CUoJp1tPj#;2 zX5{iS`o$TP9U8Yxn6d*T^)uR0Vt*DzCJj+)jP!Z1tGUbFM(&T?GnRWr(~@O57&&uh z!DchBOhsMG@T@yVlQ-Q>qtCn|7Q2EgG*2X@dDeoAuEj3%!YO?!XIX%B<<9^tb_qSx zgVxChv#SllrAnRd%}V{Olco*z-got*&Xu*LsOJ9q?Qs^c@8GEi`LL{F8bP*=9z2uD z<>-)3LSDfgD&6rD9i9FZT41MPp)jU~3cp#c^)v0L^KaU^W38WYTpnxv{3)Q-T;t!` z?f0BrJKWs(_Wk8bzy@^<{hx6Kj3qa5+Y;NVF%)PDJD_1Z+Pd+y#+pbA1=>1>87_~5 z&>rs^=ex$m5?dfkY@iBfF_0y;z_djve2Ecmm-ksU2-;aqUMiX&#MC+VeUwwwC-IBC zNh59>D@*zG?w9SdiRS+7WVc{;xSB+%nfe>T7<~r;5vf`mjH+2peruA-ig62vH8qiE zRj^FAhu>wo;hb(rgxG}Wrie!w<>vz;`SgQjKE3>oe0a~_HjU3Mzb6n$mrkBXK77&G zczS8vmh`Zvt|9!mntM*TW+zXg7*ys`ZmoEo?3N>(v!PlmhDq1R#np3hGh*j1rI6$@ ztH^?n=f>B@UvTna++%dG@^11u@jE&5$do#y!ZHyQGL;KCkPDd<;l%{#U);`=@;dor zR@Mi8zS)H3l}l{fX6e%hlwg9mcnQCLL~)rg*meDqH%a9z5tf|gtarnw-zVSMp&xx> zH8Lv+$x#OL^2oTm>C}OabsjviyT%uY_A%2^+1d>n;Q#57)xCNUcwduu9^8jQDnM5? z>k(!dFTxzS+6M({11Xrw9`8xJ|CBumq#Fc|XRLy8m^|>7NN3Im2wS4*V_7(mW#K?f z05Fz?K^NAZ2R2w177kTdSkkyQ^H}-~q}v3t^c%?1FL?UWZ?SPW9b%o-36`d>YT-Kd zDo2}+qVybVIvf&5S=X%4u%eGE#zkB|ML?NQFLl4Vg0ziX?e9pmHRhZHB8 zu3sHrnowgLNlzydmLw7B3&bD86+Jl+#?u&1o3Nblyx`fXwM>B{rrLGfh7kH}nT!su zF>2JB_wb$f-rBJ*Q4#*mt2H0qck`*+p8St5?ulbiANUn)#gSE)g7<#lPa;jvcNCv| z<+(v;B7{{<34?bDM`miRW}URMciu-dX+xx^)ItBOW+a~t%eQmG(bsn#wCd}TE?)Pw zEwk(^pFzyI|aH zGMI)?&09osK8DjR*QlvfG>hQgR_v9*?cUbvDzGoD^5NKYY{Id*5pW+< zjk4_Ec(XIrD5G~wH6nWF973Y{gDYK9k4SfID;Pg+r^seD?NW>6UwOM$!~RUMcab+O8>U=B?4c8#a~VUPcEKwl+ML z(Q063cQ$2r>f91*-O)Q+!UNl#{gK?P^AKp$jmc)`uS<=t$=BI_&uGu_1j^9ogr~NwZ+_6 zCJwsyTFd(8`` zn^(2*TNaZy?CcPLY*)t4&P`Iu(sss;jBB7REFSwFTlg2^$Wy9NeA=O`C5$i?CEp=% zt_E;(*>2@W{>2DmeFriv0^MA8P2j=G+d!K8YJqb#08BRX;vFASs$=}kElM-Y z@Wf`A5~;k>l#g{Lli2{rJ5hxB$)vR%C*Z2hgpt(F*6Y5)Z0_2tVRLov*0%xNXLb!R z);in>xK?rEJ~R98*!|sL2Ad|KOLmQCtY#dRUf6X2lx?PyZ4-gq=@5utJ`nF&AUYFt zs_+cZUh;Ghc3mWF{{|j~_7iL2J)kDSAn2dd2nldG-HgBd_wkfIb18oFMQJ&G(uJ_J zB&G?LI#@0V3GXHCmeIy~AYqk^6gVR~1rF!94D+qi^nb@ zaq+#e{EOx#KwW$=$HhJZ7yAIJ^h*P8sayss%=$qbG0j$$s(-|#VT)7A%tV;Eb|5$Z zfs#?pIGk}XV?U_MtOXv8bas+G5#}zHK#s%(CWn`uWPq@`;4y*Rr2;m%?L*-14}e5w zt`6u0{quKE{;j(fQz&?%%ca~ua{g$#sbQXUrRTN0TJFDOkFC$T*51}Nso+gS-(6Q{ zc=Wi8O1YGd;=Y6bEs}3<%-o34hvC)=N#xp6#`%nOU!@VnwDxv&J()eeY5OQ4fC*^K zg8~B!U}L^5kn^0Njo1iW$P1~AQ-p0qi*=+}4rHk>@JL!XhO8;Vk~f)gJY&^#DD;^? zd_jTuf_*54chM8R4CSmt&v(6{q-REYUb02co-KNOtyq2ws+f@_x;G@G?w&W> z+xTHZSfQ=PSf}ME3NLYB-WN^$ykY8Ox~@7IZJkVB*6G5`En`2BuDaVPUkSZzX_*NHS|5Kp7i%9v_K2_eMJpyxop#Mz}wjWMLeCj{i zr&AFw``6MMg^V?0R?hGka@$;Vs9|_q3YcFEXv_l)k4_C39)CJu%I-+P1%FKFiO19yC z3ZxO2B&Q6AJ1V>Vx7PBkr|lR?X$*7ga!sYY;PFhfNZO!}4e81{E;6BKnmYs1()*6J zyz@#ubCR1<%5IoyR!sj{9JRP)t3r8orE6^Q+3Dj)a*TGfiDjj0@p~&U@+LV((^-f# z#FSfjvTJk)DR2J%GsClXSC{idh_d;zFJ845(v1fpmHndgEEa4o{jg9F)t14>SR#-5Cu7)HjoL9KxS z)1#P8=fKe$VNL}FqFo|gTKl!L7la)^T%vU`;nN@k$ftyD9~9l*GB52c!dwLl?9$z> zgYak}gln_!=FQ@~_^xwKL!rmXUGYfx(BJt?9(6Faht7Eyp*-rmHGT%WH^{Gez`x+{ zWE!{O`{c6@p7^0K7CoUF;ArHu0#>tI*};IZf0kUu`tHD$Cuw3Df5&4WU^ zzrj{~8$s33HDH(nftUkiNC z(vYorir9Q*61^zoWi;KdsLoIRIGC|NV-M&Acn|0V*mN>zD6qUPn-3U9825i5GfU73 z@VTI3{vZ&BoFni7L!e3B0Sj%Pm2d;pFxmwDa{?S`hSJ>53Gkrr45b?j84UX#J~7<) za8``c(u8tlF2%}ocRfOxIt!z@C21xE=CvqzT@Y4FNP)e1-RJb?wFsBjaN|abFd9BE zxmh&J79&0K7>KzVh%}3;{s_0r39!));U=dBor^3}v1tNq5vEy9Y2pawue>e|^E1RL zQ3>gvbk?YrNPj)xa|WlWNSECB>Wm0)^QXH8!%J+~`nE`0>g_YL%rt1EL2G6*Fs~>; z!?^hoUkgNzMOb0FE^EeUt7-DmntAfKC6Dx;cqKczc{Pp79rtm1T&`Vo{KRE+vdD0a z$C!RJOiKx_61J_AX<1*Q_Fry~c4Wz|puE{NDK?XCEv$x>#AD}wBc})-PQJwttfg@; zk|FuSxkhJdEzXcWPKgC32Tiy)%?IZ82QsXSaA+vP?Q#*%Xqh`8rk+x~@}k7to_K|h z7D&&9@~-{oWpq%y<&-F+C9KOgUq*iwc^+Aw#`C@>RC~7h9}1+`aWyq#vb~T#O-%Zw z9u>V4tKwn!7nQ^bYnbNKG=puXrh7zc8ZlDywv$8hnil9xK#h`Oiip0^VlJoR&8IOH zC$1;ORmrcoBFC+v$IF|3e!w_8F6q)_yK|!{`#o^O@}e>-1;xUzDm=^!jHKXIM+wOb zeCQL~-J|%qgz_l<*-!X0##(7CKK^5B4S3>vyiy zNzl5A#R^BFsE@zrSi=xl!vM6wUK5y1SUbxB!VXsE6l)lPtYLtT-i-+3D+VZB8Frv&q$-+ZAM&2Z&UD-O(F9%Bb~Pw|k~CwqCGKq*|Mu+b_er0vcZa7zIi7LN`*RCx?& zTMY!lbI`ULiZE>wNE-zn39`JMuSQt%dNOX7V}8aBP{X3v_b@C>XIpC|+194Ft&0e= zEhsQuFy?~9pAgp2_+yZ^{g}Y_K>%25=EWC&?JOwG4MF5OkybzP{ah~JVd8!nSC2oR z3&USH=p&OTNNa)gIA5{n1fpC5lWA-7n67IqTOz2sHF>FS-~CHz$X+xl+)h|-H%?RW zGS0f%;iK+Qek#If6?wzEiC6U{+wsesDk`&#N^dQlM>M=hJjkkrl2Z7g5BPYuO8M(Q z@2Hdy$ImU5()>|smGxA7+O^bD*fbJN5e@LR=P7@G@aB8(jct18oVfl=nr9uYquwpW zyX*>OU%dCjxhs|vZ}L;1CNqv_tb!kSEhDZ%Z(wBg!It`(q?wFCy0@9nX$zNTG}Hdz zY%MVD+EUslB8*!DRN40SjixY$_0@$W)R&vk`Ax6ZcLQ|tz>S5Vd~vSBMfHj&)5V0dz>y@_4HkjpDqQ* z|J_d)9{H>GQq)vbCtE>X8ZnGG#71mAxLJ#Z*c6$a#z;vFvLuQ@m&_c5m7X*x&pW8F zs-;(s<)ykMHOIOCzJ@<)Yl5O3A|*8ug?fMz?`MLt6xlQug-$Nnxl7SWQ1PW?fwuQa zQtFLyViA(ocpH+EG1t9^s5m*MVYQtf#a-VbN$3|zqtL)`0diOGnDcb_!w)ujRk^|! zyNLYt8#7*5d%it*{`9vdmsk!zrtjd5JlPgE(0B03lyJfizPVkEe52TjTx{U%%uKoK zcKUqzrkyHL@ieafE_FBKnQzX1zOKrciCiA~!++eZ$Hxd|)9|VBv!{6QQ_rK+a*~#| zA)UAeHh#@WI(HjMCqC98=?~*4-=7#xBvq{f?A-&N_y_gM*NRI=D7hTHht7H17D+#n zN&2g&B$Cn`h%O*WKLAPP5Vj>mSsd7Ui0r2cmz5ag4M%Q;=|0XUr72u_YZI4MlGH3t zg?QnTh^8;6dhK#*XSBA4$+N9-+tV2Z)3@oK#Z&iZjtqBAA%3a)#TY;r z{oxmJ5mdv^r{H`J)}!O%vdJCzcwjzT(Ag&c2#>RG@JQ*+L%zQIeA{>_ zvnW6sS|*W5Sb zJ0s0??Eo`7W)w`gcZ0FdT(K74XLe@GBl}Y|!dWk&LB=DDJp@L(iL_&3dYIG>WLys{ zH#U^ZSr7fK|4~X*4OT7?qTlV|=bO{dA^UR5D>-@A$;VbSztUp7_4P6=-Kv*>DYR-MjA3J*ck- z{Md!6s?iAaPFJ@AkDTt?3Yt8_*BadA=x?h0i?R)i&*xjHoRK>Eik)j;(2fh0wftJ7 zXg%~fA1TMEK?bHlLfJMvCw@k2C<4VfpG=E?Q|63>IR`#5%Hm%f{fbP>sYPn{kS8@T z?W}SWs~Lwg4uZC~cl;hC5k8vN!Z45tOG|&oo{XD_v+-?ET{_@(ZXiYh7)!Z< zEaieI!%qn;!yt@If>1hn5Z=PaW(imXD)iFnj8wksw_kb|wdf`yz2moU&#KP+tr$E)tJm{a=Bhn1vP&3UC zq8ylaMejOFm`m~NPfD};9rviNk*f@kl3?2mY3(3VU^N?{@+Aez!2#ougUJZ9rW%+g zO%8^&2qWW>js>Q`GdYV9_lXaZ+`(a3>>3sXH|T7H|ni;fgqf zx=xyr)?Wu?XO_Gz1$kSyfU~C%bglrX$@ZGWkXAC za$u)aVnV)yDeoX&T#IM(s{xG_fSpqB1K26`8v!ft1sd~u9k!glCBb0>Y~7YnMo05B zI~$54nyNI&!|EnXiz{;+!axKXKcgkU+(ct}+uWqiOWW<~Q|HJfEJ_}zQ=$Q2e8O@@ zg$LDi6qm4LM;AnAJ7tX=LTed?;#kYLl5q)iXc3R?BXRM_OOq+#n`G^SIrNW!HUb=7 zygyT=dIM-TSq?C-XFg*+<5b2;P-W@jv0BA+YL!NW zF+Br0DiO%ySRlhKsPcSmAd6{WgS9n*l{SDzGV_vaojWZ*PIYICv!|aJ-!Ms`k?F^D zg~s|0KARRi@u#d0sY+5~q(*`T<_e=M14db*qm2ZN5-mZFHWK*d_ET#rSYcO$WI%4p zaeuMR5x)N|*DeZOKspDWCxP(liNc{u1%Ov(?(HZE(Yq05 z$9y1%PQcTr9>rh&Etc(7cbwvBm#B9p@^G&WzjMXxlUA(XJi(rfn{*VzZFL4RDjpjn zaq;?>BpLNhSnq>527G}5A5ca=9mu9JP#M)HpvE-YaC`=0saOSMv=`Khkc_yBI+L+M zI>}hexRP)&yUPPZTy3x4B_416)}Ni>7b;W0H`x9pm#cmuZ=d-{5whhZ5-q9oWd&P2O< z7wEs1LfspISOtMt34vG*B0Nv&+NB@PXh#f_=Mm$!4|Ti_gVKT5;RD}5RcQAbsmeVA zmz?ijfUFtmPuBgm5&kKDktvY1coGAm9)hG5cTN>Pq7L5nY-_6UoAGSBmdzr%Ls#{=)*b>RlgJ4uJk z?pS+UH#Zu^Qfsa6dgfZ5G{wI7#}Uetrk?m246|*E@hOe7+^EfunnlPorpKyTHIwfl zEsXv)Q=jZ5eayck0huCExmo0Ck5EAJ5LphHw2WzvJ(tEPzWs&V4)cfi-PUyy$-_zB z5XUoCGY*5cwV2wv{eslizF87aTbp2x18jh8eNiCC+d$j;8K7;wClHkym^Rhg)|P8q zPXwl|BMuGW`+%-N;-#-YV^7A-un3Rj`ze_<`aXSh0JN=5Z(H{U;yVw-(&RnIfiHsU zr+*CODt{2g~{ zS|GqY$&(nCjMZAf~#F9EhpzJOPD5yTIA$CivAq3OW!@g8n(fjWk0H zAEp^f8=CclE}3@M8Lqh$ABN?-d?}=C0n;cJwLoU9f%HF6v6vL;M|+H?dczS;pSSVK zmca;@GqN;8b8PI5^ayugp3za$&8&j@Bfed_KaEy=Opf?;+@Gn~G{YV68EJ-6n%L%g z@~69$Xh(x4hl^5Qlo~I@Dea*#jv7xArH0aWRWrRh@K82h7qaonh7MmvGfjaCp)#wb zq`0b?lvIsYg;e1TA#E8X*pSu14gd_S|S=-r3n`qv7m~ zxtcahwXw2&dX_0)qN_@|R+E%#L}_koQ;p6ogpC!hZ^BZ(0!q0mK+07CQmzV+@(obR zRROi;vfc(LPxIp?C+^HLowq}InglPNf;!IQYzD3KpATq!E};H50F-|g*qT9>?XVa{ zG=|7+t9i<+1o3{yl9fDV9gn2> z>1ihVZu&A*6Jiot3CRkD1QTuMun$E9dLwATtr?gcPr46QBg_~cC9`}HVFiBBsfE!B z^O~41%^7%nxhVTMn5Qv#<0H28r7aq=$A!a__Dl$%(YvNz&P~(Qiz7inCd(3m={~WJ z)W!|QrRZ`e7S3PJ@kh#}WJ?23;!sQT8S5FRK$XHWs8U#pFuE`hT^NWi3`}k_T`e<& zLl=Uf3&94uP(T*~Fx1Q~bUb6UvbvD4f1wL~rY;Ob7X~Im?n4)nqPP`ystdM;DSNbYWof-Z@%L5Dr}^stbLtE{rg`Fc4iBn3nVvjs8gYm8{o?x6fV`x{%km z>B7h^>q4KY3saiVh3|VOy6`mFzVG`@G{IsTLwQ;(^CIh)QxVS$$S^&i5s2hRy0nio z1Y|UQG*(l~2HVXf(`4L4U#E+c-^-eS7e7oPzQ!WH8d#_-%`H`0m|uvo=9nx$709@t zxkfJx^9^COEQMC{PSqw2iuVvGEp6TtH!`keTrofDS_Gm{K%4S-gmD)KCaXt@^*x(% z6;xv1A9%EKGl=BgBybBbKq51D)LsVt^Y>Eu8%9j>yR_Kf`XDTJo==p|`8}S0b5QSv z{#2a}Q=v4SyRUD0>ddkLLYzFNDx-b0wRKEF!-CF;17L5ku; zS7Ek7WSF+kml?r8OYgoQ`nG-BFUac_v0{Yb>hz-SDlWW)c#0g)MYaRee%)O^Y22Nw zg6WI*;rLmV56J3D;6fh%*rOa_G^R-PQo4?;8m+7@i&<7rzaVezyz&!L-4wyp%ie<2 z?snzPR@OBHUk1ev@f=;R;t2HzGVDZ{4-A4M+5U@2^zwY;gJy{#G6!Ky5S)g#H5IpYv% zq#!yCzPj9bjtAm{1GfgHbgN=>b&W!qZKUe#T&gUyTsDhlBQ;D(Fe)KMA2*9kzbsH! zD<}0p*c-@*0!qN5*bJ4*xs6kafYjLYMT8pd9a)BMZN|O&-6h#L(51k2qWPre_Lkmu zKJL_`d?Sr7%Ahp)YS2jYZu*?Cm^Y`beJSl~wmP%L>-(;`_?an9s^8gn4j&%xxwmc2 zQ*J5u+xAljKiN|J#N(2R|G_A$OqKYOhvz9C*}2;kD{f?5%eVqMOpC{UjEsxtJu7)8 zebXT#F*#KT`b{{8(I#NR0gl!;2Og@tA9S={4P-$BbhKWNFn+N>7C-`z1X)g^3&N7O zlyN>|J>wLp;#&?p66v%=7ym!%-Ud$3tFH5&l$M5xU0K2 zzQCOg%Z{+XsMtQ=@9&)Jo;%5eR%QA4;L~h|8uT$U3bD%4r(E06UHuu zJnVzc=C2G7v|a%*fHw%%On{*=b6O^7nUS>2B(}^u+#WCoTV{?fCN061r9@QS8t6l( zMyw8Pi7m6nwrrwuymG8^6xy=A;gO`XWqT6la1FF&?Swge6CP@9hqmk~;epnZAuW57 zU;`P@GBbC*T*${q5@u}{a^eD>Z@#+^{W5bVYay=o88%(OLuX4OA~MkZi~IP!mPSx& z3JM=Fb9zR0C-?ye#XphfWo@#45$}HcXE&?Cx#gn-cXl7kai#MsdtQ!r&u*UMIR%gK zsf2rx+m^%kD%tlR+dNs*jc2LqcvaOQ`b&u!s9bwk33s5XD!Wrx6)p9qjm|HPp_@&E z(EhpL@g`7~KNlWseI6>y-H^keP+4A2_*hC-mbX#^x4Nv6vb?R@IV zsJ8yK{d{hY~nUKyeD2 zmq&0<(i`Qqk-h%yGOfNVQp5KfJn$;4dU@Pe&6T)6Jjw!4jhCWIFOW5(sh~%-DoFtlQ(|u^}RLOW2v8OvQu~8b@%BP z(SUz=JttBhHfTbOLda%zi1#I_XGzzqr_t8a8r zCin;U@piMNk4M8pt&xxgZOBn5Nhu{=ukJ=$cat}De@CzG-%;zn?=I`!7zVa4YGx}T z+Z#|BSPgM66OZXPj7K*V@*1h}cyt3HZt6n~;{r%3XSzO*&S)!V^2Ve4zCZD)73=-n z(vY)fxwY=dducG-3NS6F*4pndySh%9|hV8T(G|@ z*w4OLj_{4NJ}9h7#7J z-(x&NIvyTswL@m%kc}frJ|$hJF$6{{2PSWX^y57teR37jPyG!-%0LZ~syXTKlVARN zl+MHvAzH ze1pJG1$3sFTj+S@7xe5Rmf)54!i^`_+Y8ScVbMIwqM60= zE63UkuW8@wBJf;PSx3{+)6*%VM;E66x;*g=LBh3aq)$~zpQ$mf#d_HoL7Xmwq6Mxo#_zy4l&3f zqYTyE!wDa2Sp)srqtKd6RE}4URgOZZoW0?(q*GD%B+N0J5Q83K&_kpIs;Ey1kF}l* zQP?L5)?@&MHFFho1o{t!FzSWX1nzs%*(R{}-I|=+>z|6cfBfUc88)ZJ&Uv%dQn;+9 z_B+)k-sZ-{C7#DUh8g|g5G61kJ3SP#*A=o5kt#>R3hS_oN~0A-lQ)9+Z5Q#;qfG>H zeaI{@{g9k&umfqAXngG<7uj~0&*1Gq7uj~G12~p2Pr5@6?Sw~a%idE4Xn{qv>F16v z)2%#%cZ-bE14}`E9GGYDi-b5mFq_E3GhpZJ_-l5~wqLz-cI;JSvr{+LqvoZ`_=?H1 ze)WoTN3@{(#^)71NSaObpjkpRvr~027G|=cgb=KeOPw`_t9o>dLMT@&S1OmG>LE}3 zB=zkcsXF4F^r;uwJ>Zb5bAaKh+bh%SccW{9prb>x*Hx(v~g8w83D=pHjaTMc$Mp1^nYJd!GA z<(Sy;Bk$ENz=ywtOM{5-wEzm5kZ7RcjZ+^ z@tD`~5IYfKCqnFmB%hM5(nYEBN@k_czJ)?}!L57+S4gz*dr>+RU0oEgKT{NMG^W0*3Ow4us@%C$jz z@ja~(!tN@VPB&^>i02zJ7l*i)Al^idPRqjCl z={6FZDg_W6f^q`{J88CMlBmlNL?7)sW*m zP-(I%`W(lNN3)g_=AGQ|P-`haw@ui zcAKqtM1-pn+FeV;+s#LF9hK&0XSazEr8HAie>}vS4cX?9+-JJJ)wt0%+2l==&otjl z%q{3~$gL@|u_}u*$WL*bYLfG22EodBDJlXHdir zM~L6k<|Vww4%iP(zGqd6^46$i_g`))-xltYiFQvmNB#Hxn1h!q8#`+2c>=-2yVAHo7_A*5>z-_Zb@vVx2vIi>o6A}U0ZansKG+_>y zg`ABEvBM!+1{J8SA@fJb{BfD_jJ>41lCIO(OQRJillR-pQf#r87GWBTZj8sXuiD&R zHa78szGBw`%kk<}x+JrfQH;h)<#Oc`)JDkeH9Q@3SiI?613`D0W}e+V!EPSNZXVPV z{}t$991WR#pl;&vgjo)TZ0(0fTB{*j|InH(R?bvTRZc*K^Kf`1>2xD!6F$~5y@NNM zFvoI2Oe@T>9O!YJNr;fXM6jm=7%?+f5CBaw(k7Y4CV7jCi%`9zFia?H1Bu=GO z$kF8RJ*i@jCMP{>c4!mFE5|BFD~F+axCh!))2WB;kiC@qFaDAntR@^3t38qoLdW7o5J&z?Y194M^E@`lHJF?oB85A+pM5BGsi3rOBJ}~ zDsV&QPMZfR*Lb1Pa0e=I@&v*@(7n_*u;@)QX);v`&B=*^gp9$T19#v40_Mq7$df4; z8xZnj3OYvX3FG8aex6K)M_Se^Po`?UR!L{QRw|b(m%N{`I29tUA>tb%&d{+qo-pDb zBK|N>rb3=fL2cwaA!Bj`Fec`1sf(2}l~a`y&|Z=!IHWz${p>#}x>=|i$;1%rn|QqVggI6pE5$2E2_MUe z@DoqP=lL)Z1KqX%_H|$3dXA#SwlrCmTTW~@NgScyDM?r$o|ELG_a(`t=JM_&8NBIp z1Wtc||CqMlA?c3%SHzda?5Oc{LDkoJov20i->F@Hq+H}8u;_}!F_GuC;zM6G>2UW&SQtQy2Za99KE)pZmLm`yNu=N$n zKH^Uz+2@Pxzv&78m;bno>PxU4ryuNF9zC(e@676wacowqo5XRm(}8I%!SNlA#+>ETnC*i_rPWfvD+Sxk*r*- zoPkc0$DyWqIXu!j8lpZS8%iNwQpmtV-PPAatWh{xu#I%u(nZwVW`U8X-13 zbFO0&_7_ftn2V4{J(8X>UEltK(e}B?n?C=|yL?cJ3g?!Ftb}rFdD@?A_iSH&u{SM# z@YDtD%iDXi#=lLH!JCG#v8VrA>_oFFDTeoKJK=ac**o6FQ1C?v!fHb?6fC~d*GO~X zk5xm#i}?v3iJ{PO+e5f96!ru6;+Y2LOF-?y&|T)*!?hga|A=-x`!n=Y@R^ZS^3?YudET{DqrJ%M?kJ zmc7Xqs}N!(LVPuew3x0>T8vg&Ox{S#&0jCl(#|bB_6adWvOdH}+&xRnlP_sJq}flG zsph3f!D)Y0wbwq{Vwh~Dgt{kcyfJ(yl@JP#aI!PTS8m2*NeZOM0c*nh zZ_oQi6UOl)X=68hyO|}3fF&skYi?)#NSb+}*xj1tDadU58Nllkf)8eVnE#rY7iW&I zWkx-)2+sA`oajt+0gvn?mz>j9Le2_>JckZ>pdzVkN!MvSGNaW7lUHrH?Oi>OY}9(- zk-hg+@yIOy&KWDY<@AN>xK5f2)NzSR*70zNT@3LOK{+ixOf+Lefd{ zDd~E3H`=g)|B+Uq(n`uYz{6aiJc)rESb_=<5{~7%sU!4AQ>b0L0meT(lj%bP`zH8B> zvnmS9I?RfRVsu1L8ISc`H6ES}54Bc8v@K*O2P#xYLuSg5=TIRlG)Z|SU8iwGjaH~k zj!^Z^wv=LvBWe+*vFIvz-A6on^L^8EjOI`OZZVp(-0G7J*?Y3lw!OKs@XX=%!m~MM z_WYst!Vk6=UV)8d6GD-sN!vn(IK=l35gB;;jRwd^-w++s^POk^RIq;vTsRfKmrpVh zb}){He6let8%+Aa#qVX3Nc^pJRdytPZ#C{yb^@X^Q8`{Y29?k0kaII33k&G@ncnf+ z6Jp^*PKtXJ}*yiUsDtOaz0 zR>MQBlOd0?LYDN<`Yng-LWZbL$fpe4%LNT|2Hk0=Gc3)8SVUnZTH$IU_EM6 z&!302n*Fo1H5t5R2-CQ#!iP*l@pcYP_r((Z{AY2hCAHUXKuVU z&pVrfH#mXj=K0XUxE?C8dqPw)rm7|rzl|wMkE<*Nu zLY`efZH?)bul7lLqUW3XL#b zn-=S8MK-st=F+|VQ^gywz4lA#hw{Q|VJV~8w6ZPnfo$qSLhkOi%zyppcd*eav7LniOHRi)Tst1QAa7TwsY?pLu@ z^kkbjn-gxLhj?5MN1A3<=#Kr}=MLWVs!PgCKbt=0GL_>pYDsXoa;S0us-W`3b4lDC zu3GAycCSlg#j-jFCY)z!DOhL%H%;yfmElXFW3)A7oeI^`Z3!Ps@jk#AN%%;rmj%w>8(8; zOLx1r`@P>PzC_<3uY9yTp75pN{oO|nzJ5!c^^IsRT>SCn#qu_7o#5)AbwB6%qD>^| zoMeMznRJp8aPVAx5-QUcMNG0cHD(JrM zV`N?{-r^iq*26>PbNGrWY4Cd?CATHs8w4mVB) zu5fk!<~#iFn?BBe$L2qBC)hcE(X7VS_(&&sXE%7SEA;lld)Gmo zH^(SLIUlVYt{j3{vB9j^LTrA>To5v`gv=2ka|X<;1T!l^g!DO7 z#jHwJY{sL{eu6A3wh(_cM72W35~_+vLso1dE4B~^Pf}h<*J*6D(W;`!`)%~8+*Vg? zHH}|GYi#t3@#Hs?#70%l@{ML_yWKov25_o!qH-L@H-g=tTfZwuTD+<0F#5wZ^Y}&r z-w0@JpAX|3!T3fYGgOFg1mhbejBga;8-@5r&>D?Zj#ds=4#D_FA-++FZv-6`)5kXo zk-`uugpoq1wS5Fs3I$RKltSR*enj%kGC%rc9L+QZ%{0Bu^!7MLFpg1JinDoEqfHLe zYOQtK?XUYdy_5>y*qYE^YUj#}IVMSy2MEYD_Mc>@a@GAPoT{x!%zPLkn8e#;)7#{3 zh;tO;9Kkq8&?bKx(&RrBI7fgcn|U0dkO?M4`a>Kb3Oir9x?lau7TWznzeskSUnHY( zjd?UTMWvaT-9+Up_EWp&R@)?=wrMNo4woyJplig#(B0NW$(CwJ*z4^NQx*T+*Az#M zL3?s1!(`YTjvLQ)b2f*NuqA3xPC}-r+-sbgZJZrA8{%z2HFrAXVPnYS1BnGO9eu)2 z0cP?vT8%Y%GkG3h@;vc>P)_%jk2z_cGo4)jPs?&+CN(MZ!iL~+c#rrC$R18P{cs`K zQ^JZ0+?tP}#VL=x9BD14Fzlsa#8TmN5XP-H9IG6y9EKXaJy1C_T~sebl0%+0h1lbe zg+Em89tSn}kA=wH72;s41sEqYx6pygHTUEhpCQ8jQ;L!XgP#hGu-9fR~$3rHX5K#?T1W3v&={k+UH(K#AdB4Fg#TJ8a5vH-|#^C?#{mYZ6!^GK~@H|&! zvCWqgyBMD}ba1V`u)EduD@*Hq!S;XcU-_cm+wN9pfA@!9`Lk+iN1tu#4f<@yRg?V8 z+4@Yrr8#iJeXd;jn5`T5*m3%X{~|&Bmx-|^?7hu<%Svm@(+2i&z-DKQ+wW&yx&6$* zar|j+VqrD#G4gBg|H5|b$d=%~7Tbf@`JV)D^f%acwnpb?9>DFhJ6qqAVDBSeJ6pqv zxHH!|0!`Y!Z+qRjvvv8FnHyV|yCFIF!L|LL+tPi`kJwk{PDc#2=kDCNzPkTUw{%}~ zjc=-4+@crw|8B~@uJt{$Z?OCXt=@2Z?hTrd_WnDze{6Db$Mrj&zvH^+^0Rf*M$(e} zk{hq@?eiPK3ErCX3f%|u#j!TBzc+VtSRQV?|MDq1*6%x(n-st)uJ2mN0qEGoU$`T9^T&hR^hB?of1RG_H4DF>Cwk4o zn^%L|I$$?FHF3?tyW)rPLC-zA7T#7G^}#-8bkvGrCWb4ADhHstxgBQOPxweS9bJ5x zUR~W5a&99m3v+GLrG&Lj_l2{p6$L9tKxdk{g<4H%t+h>7&wtvcq`@|&LL*H4-h*wj z!kgPBbLoDOuH2Y69d72$8_UO}Qw(Ed*J_VCA@&C<4i;~l?MvIdk}$j5VRdYs8WYCe zK<&}t5I-?}Xs zH9-=`bi8t`a@1RIX7Yq5c5qYoH`eOJZ4P_^bUX_<0+eg2d0s9RylM)Zke?j#bs*@( zvpr+H0&j8-G(cNDhDdpXr?Q#5yDQyrc>Crg~z_(PkZ?! z{%hQ?@P8G~@@|&k-7G-gnR)kpyuoiX?dRI&+WB+rIgr9$?m(-#TDelWT)718N_pbx zcIDpx=?dcPF7zs0=948ObAyZ5l#mX5zph)+kHa#P6kg{at^2o=qVJTdx3lsNaz_1$wbOWt-5-xqAM z?4)NJ?LPODflVsK%z|gasxK70NZ9^7Ea&2?P_VtnR|xIA_F2Df)@uuGoAD~Q79Om& z{7^CC)x~=49kaBk?{jFAi#S@_tOV{uTbFU%pUmB(^M`@nZCsg@?LJ4_BEp>I*{i#| zz5K}ePJUpRPgM;bI6+N(rdcBHJ9yJGxX<_IcAK%~aEY5Jw8*k8HqQ$jURk$g2-jLj z*M_XELw5gUBb*x_mo!>oF?r*Ry}##-@s&b)`1bDZ4>&E^T7IF7I(+Q!UClB|jX%K# z7A4qw$HQhblBbplcN(8@nV{8Nk=18B)2rsAiM5*VJZ3d-bWrxWOHf>|T?<=wE$rI5 zYvEdcCT_>JYZi75dz$lTZmL?uOmmp9HCYS~lxd$VOqYds(VSU=*2LtE7xLEsdMN|9 z)89{SM5}P43iqRUs_-*IyB7ZMT7}uA{{T~_SWo|?qv7l z6IgeyF{V%Y^%vC7^Gpr%izthP>d&p*kUyCh@p{XC^_QQDzeG((6LzLW=iW>x-<=Hgs{!GMaQYt)ONlxI>t9f+xJoxXDH{+nGXNW3#0}cshS+T`~X25 z`{%p1X(MU7nYifI-Lx2$LzDKo)QR(Yozt6LQ5;%VKFI%eZt`&MSXfNF!!lwR2f=Bf z`HIpN?`B_~IF+D@d$4h2%X_fv&8KSy4?N4eo5EOq<_)TT4y34(RRP6$v!C9wj_oUXRkC=$s@v-uate*1=-R)3wsp|^vQ4-CLr3fkCjOjW-~;pDgyG7Ra$~W5%9>uAvZvRU zeuK)UE@1i(?*h$B+)ooezI&RC!gqx)xM>!KN{)ReGOvbU+QqtcW z!lX;` zTDTw6TbUr(#vw*8c%pI~GFAM>bNi=?Hxa5+#n0tWKQ?@)7SZJ0C%@93Z5{}+VUs)# z)vweas~^i`ONn>|#WEFuw&`fbScpjuapObW_^>QF^~`lB{V={|#kt(Svc=KM-%l;+ z25=iqxcn~`9(L8_(--z#we{)qH(quBXU^}u z>H*K3-+k2spE-ZsRhK<;{)JaP=;{1tus(YnAcxsoT7zIGd{B6(by;|@^}z5z>jBV3 z$Nk|XC2fTclDF$OtRpV`#NbUCdX6O;ZtVQV&nGGXYWiVlpLzEMJi0Y9(DZjhwq`=M zYQnMD!wly^ptCT&17;wY%;vFZGrB^FYFEYmLeo^!Tk4 zd?P=9pjEyg=*(C)aHnx(x9odjabgWzRNfPGzS!Sw?fah7Z%CGzqG<^W8p5aVcOT0+ zCo(^8QpC9^@524;Q*ZER8-F9cRx)6_*Dh}MF!x<@+8-NflWW&axnHGHH z)oT{pCDkqIdet@B>YBW%?(MzmKB89lKOZd{VWk^OJ>m;qv%oJtkMsXMI9_{jyrzbC zEsWuewsA(saYm=AGkUCUi}GZZl3ZsGha46R%Zg2VwUw~KFc5O6lRDTJYaxd|B@HR* zY)B?7qiu-EX-IFv8cjB3!W!al(^zzEYaCM86S>UAiEnGJC?Ea5!K88>= z(U<1WII&vL?)9uXG(a7zM}DW^>EnJjnYzZtqRoxXJy{d2O5zhHUBYTwy?(r2FRbgc z*WPiI0?S-DLxKUHKj@(=dV1p{`sJfa5`Pupv5>u7uD_wYv(?gCJc^%8&Yut3_rm7S zpWOGb(^r%Kl`E_R%#niJ$7LECA=DhAnSPs&`94Zu)iT;#?Ow}!6?rgp_RUFS>KOTp zTh4_U4Cpu;c6?>0(!MafQn_5Y1T9Z?Kby1oM|&cFg}`c^2`Ok06(QPS)FSZKDrLF!AdQi(X6#@ zm(r&cTYF!6E9r`qD#hmF{Pg*dUpR)dd=XsmeQ-dc4rt%ZabNLl1~h@bfA$f?SF$0Bu>H~f)3<+}AMV}v5c6xFHN{^dvsWubWdwd6d8CooRyIi*G5t z)3%0V-An$K)^d|#>6-_#ZyxeDSDNVNd$xlIE+^LQl9GoFv&{CwYfo9=!W%ll`)&2^ zg*Vvf-3!0u;I?0T`$k!eRGZ*2Kit5 z-Z<|qrjlbGR;y=gI1g_+!pgHfVnusnw{c1#^~d~~ys`DCu=OXtjfn17{~xBLx8~Em zwNDog-Z5%z_TP>JSpGhJV-j3^#opV;DPnAUkr;+-g*^PHrCE`P(kLt7!NHsV#!QeH zgo@{>RxAnj{|B48A?f z`oRMaO8@xDrtH*DvRgmnH&C^u(MgjUgxGuryNQlVsHpIq8aEf zIncx0-@WV|Oy+c^o!acF?0fDc2ITo_U*IU(@X5LVFuDJ~4@{!3lXJf}`TL#8x!bQ? zncV-^Lz9C~zkTunB%Y+~=T9=c&!2oi(cDQ|rv{%HIQ>O*bdtObHqlrJ)&FC?{iV8} zt3!bq%6=}3=E=E}X8q%nbE~r0ut_B7$0unhK{gfj6QVGbs70Pco_wA=Nk3jJ>Boy; ziSaKaazifuqZg>17bf?2woY#BEDRp_buRWS1>2Wp#=4geoSggJ$^Bm(m^`6e{N?2D|LK*VoZNqxLh)OZ-+nUvc&IAh<&>iQ zdwZ|5@A>w!B{z7W{3rp%#1lx-{i9nDIZVSOV_49P$vs<$N!52!&E-Fhvi;@V%1IU6(=V!K zA`;z5bA7l`&(}P!oUoa?kM(U%GL)73F zO^kEQ#q#a@XVov>4j%ZET-#U@m|>m&XZ-W=2fpFKIh|C`uXUgHJ7=GsO~p_&hnb>H zGk-9r;^xa4IiL1=WX=zIc!bV{&yP zut|@$$u-nbcA!stw|U>(k$Tka*7{TPEWe1?(OpkEj+lm6GU!Qy&xicB4b-s}IJRJ} zWZu2vKNtV(6miZsvYEUG5Bw)W=P!QQ-!wI&m-1=%R_j4Ka}D)q&dnGOqeGQ^Dt znXW@z21!08U8mWPG1?`d$r~~M_dN^ySZb(+{qMizdbf;_oo=Nv?@|FfZoagX;j-O6 zq_-O!HoL`U*-Z}HTF8TrbTj*+9#ODOyE*WL2C@-sfU;~@m98aja*P%$XDX-6j5fRe zb8JkPqYNRBVncL3MCU`cETB*7UkP25e5-%W{cq?_k33!AN&Ohra;Vzjp}MnqtR9Xj zyQVgKyVq&_!!FNde1_#^i+VCtSDTpoT;3Gnn!u!Ih5LM|Ht0> zpt|#p@>Bz9Dl~2%@ZkLoP*r`A!UY2!>+!nf3OGKccaQw#YN;?+98=HlV|Cqbg02pD>EckUj6 z{WN9-WqCDN=?fCmEyC_bSTD9*ldZFRq1W+-F2^Ie}o15kM+x?gVb&R zVc+5zVYM0;dNes)iz&q&+O#+Y-r=&EHp2n28Cp-9;eeE897{87?m*&ewMn05XbE#) zd3>whuiHK7`skt~Yfci;Q_WoW!?zTtVQJ30B?&GUKI zPu9;JoDnxwMs@L6nP&&5F1yFdJ{`7ZHAx$Xy6!c)@F@=azWZm>#td`vn;V(Erzakl z>xlD5xq|iyLLVt1LFiVXP}`-{p6chQOi!z5Ol z7Q@Chu1k7c*lFup5_O72CQhN4<%TL0xBXVnmuOaNZ+pXCcT>@H&1>&Szn(tM9hjuuH-#{14k#wU8P0PMvQ*`Ya{0YT?C9+3Ls6GoJwgY-+XNb_(-*e%L z|Ha~Y6+c6*OT9B@^~s=rpMEeKL+3QJk!aqjcz0cQmdud}E>(nN>R$X@g>hsPcg9qlTG>)e~ltM3GK%vIW zLBB2JiF4SP!(!jnsgkbhREbutOx{T6mhWgbL7K;U&-#U?u2jM%o0DDUsy1Chyj|&x zG@JO+7H5oY#?!_j3SMdqOF<9n_1v}FY>W`1iCqM+OW)o82%-C z%^7$tylnRq`?n*$n^4`3cxe9Y34HfHca&#JUGJm19e5@?U#0)MKa^hztA%S?ET!~Hl=9Y(l~Pt~DJPRpzfPU~GjSWb*4uc!=u92~X$YG~8P^{uoAue2 z99OHn+8yQXhtog!b!Ira_mthsFm=++zV|{SXGrF5am;N3SgSVzEzYW|%_$~8e zep`Y5%jQe{Ta{krZ&lJR{NM2;s#zaWjFw`ta;9>s7JoG4%n#He98UO_q|X-`6J}-$ z59AG8*M+Yntf%@4ILj{)2>fn9+s)j;vW>ieRa6JnGsx(U!Nx-W@V{Z~iQCo}_blR? zj%F5nB$v)!Yhdkttr0^i2t6G1hNP7HeX}dvR*&oPgR)tEluO0>Z~XAdkDYgIOYgp` zbFJCkEb`Eu{0oJ$1hgT_47=S;o!wG?2sN6#;^MKv53O}y`ag=7wv{-$CB#9wf8!@z z$^{xDXv`%52Ss2;e~teVN+)cbMu`H}8faS0@&cxH-4-CxYazQbA;(NY)}oTmE9pAT z0@CQXv9caccy`aUK8{+s`|*X@yY8+-wB_{DKeVc=sS;j!$VN@bMoq{hEU9Wq*Q=`0 zR@LN9RbSDoYIDYT2UXP}DJtHm2e(lF_W}9qmG7;5Pvx&wzFYdF;HZB@rIILT0a9U~z8bS-fB7wK_ZJZ%Or{#D-^YX}vu>)OuTZ zu=UpPK=)Mfg&%m1+xwg$Y$TFBiBfH!9`_SM1P|DJxoO4v&D#mN(wgHm* zkE4j~@2cXGQ|nJ4RK;a5e@0xqtsGpRKXEB}eWO}5&iT+}}2XzUl}pf#hO%eIcRdn5QeW%7HR-MC@x=4$PsqVD=nl`XKt<(Uka_c+ zg7?}0%{6liovECHj@csgpKm}V%?K-D(~OkOY~O%dHSMNf_A{66zdum+RXGA*9`MB1 zB~SIa85UD2r_*8C6n5BV2`i6_lE&}#I*pu2m|dIjPjUh_(&5FBZrCkOc4M!2N;deR@+hi%*=O%&H@u|Rsl0Eh!F}PeqBm4SL%|8) z+q*aViA^F>05_~xx4Dp9wX$=Z3nf$ICh2`Mot&F(grG@UoVU4cakyuj6+^jt*}jqf zb;%@Yi+@esJZ(#PRz!|4Dy~PN;XDlsr{3-0WQq9#|r9Zu#kJGQ5|M z?pIO(-*5;~_OP5Xb-=ANLd&^1dmp{3rRM>PoDqR96chbkn zGsi@RCU?&Lr@3)T`qJD``{rYDNZU8_x(@etU-E4aya)f=b2ZjjscL>Wym@Z_rPQ># z71X?u#7&kD-7|mGC*ai;X!`w+unPhs%#vHJc?W-UCQ zrBnf7N)=d#giSo3_jOvq@-7`&Sx#KT91MLPwsIKjUSLlf0}nM*oY-P)<$S60ay! zd*s~N2BTJFVWzPk_I@13^Tcb6D~MM+x#817#LM7?#Eaf6*%o)lK`yQ&qmqU-*Xy|g z1nH!?&%J*@^s5BEMDAdN4%P<~d0swBQr8N@l|z-uf(~?yX`%0e^)K1U(E3~ ze`k;fKQBA+bq=z0zw+Px!dR?dDak3xbnI5Y%=$ZQmKGxfJ6tooTDby0JkGgM_wo$v zIQ}61+x^1(Ec?7ZUFI5ndK@@n2I*6S7wpr#>(X@SP8V(0`JY7JQ$O}S|Kw59aUXWA z_}B|4=*HHnnVj^Q5^+2E>J@v)>oeOQQ@x&12i@mh(_hEORdXT7*)*OgL;q0o)L5BtC>{l ztjA+xq*e|#lSV~BsVqM_`9@x2%I$XCgu<5RYl`WG1j`H*u=0D1*OEbQLtu?sEQi|TWeibh__CPrCh39gw2+{%itBl z&5n|$*Yxh0t2jP7S_+HuyXJojVfW163}<;5D0nmo7z#62OAkxLvhDn`lmJiJ`hLf7 z?x*r&QJ349cq^pr`vt?7m5Wb2^HAFFu#p+t#jb_Cf3a)f!)rudvoM!2rSE46x<57(?!#1|G&~yG z?W3XQf&FJun_<;vZ#~Ppi|?sy`RHG|7W?FFSLfE>x_hj4E zn-XwikKxe>ik%gu?oGk06--SvY?Rfqk#^>I$cCEnH~wL%Ty4ow@1T_MtkY>+ppL%+sN*|^^KZH-_{i(P)$=#K z9$d3rz4=&~3S9;Umkzdyj)g@2E_ap>nEn0(#2ma9GYl zX|iVtYqDn&&(shyRfJ_DNt69j!kTQqT8F8Ai8$-?cosTFY$P2z3ggg!J`1q|ehn@a z8sT|G!W>>O?WWH{M!C{`%TE@4u=x&&k&f8*u>$6g>JBF55(si0Oz0q1_llQObOR;55->GRVx>?hIs{XKt z5@K`Yj)e!dcFbSbx|j#4txI^2+PW_fQd^hW54`JtU5ih5y|A^-exOHqt^1kouM!AS-Iw>V#j*VKAPxm2B6s6+R%C4Vk76w?)zL2-v_I+<&+2_xf@4Kw|^Jn)Bo_S48?G0yMUIR=S z17}`RE%Nl%x)_-na_KmJV&f;T2Y&p#g~-NF+Wr~O{Mqb&@R`f{i@5B}1NsZ0A3b}D zV`R;{m%sgQ^Op7$0ed)%LEc*qEY}a3j5Z%M*_QJKp?S-Ei*}ZTr$Gcy! zoq4|b=F%EPImdJ=SE2e8p6y$Eg_d|l0noC>x2|_|&7{Ud3;7QBAAVGu%Hy19)PT8#%-P zo3+>_{E57blUj~dj#dssy>D5DK<_1w&qikpCmr6qWu6L^s2vCyUVe z_b{h+pe=qEq{Z(PoZ11j*vu_-rg93Z^NY}bIkn?8JXJH!shzM?WOGl|T)O{b!c#kZ zLMH9!wWp9X5>WjuvWWtaSXrG8afCxWaj2=WDz<;G@w9&;VfGn9CZ~|eDMX;5rf7SJ zO_cZwN{9?ddrndm5ZiVI75UpWX}aUc&1l9kB01RgzR-d zh4n2^VSO`1Sl=YrW(EjrGN*FoW-3dI6%ek{QWMcqZ?UE3U`x$0+gM>O5%DWE;a~-s z*fMKq%}=EpZz(mW2W*KgovECvoTwa!isvk}g{Cu~>5z9nLta-254F6{X=@L(#n(ey z{Cr4@pC|aODL{+O+`>mIhoR~@2K}dcCJpLo#;T_UdZ=#3w3{v@&87S7A1Uf-do^37 zA24HS|Au;+Salf*QKt~~f~x13r2X5Br~N|-qxK<32tppHkC_gEhN@>bq}&im3^9X} z@=Cf+qn<{qdM58z&r)nrPm3^(MK|jCxUUxVJeh3SjDh67ND_oanQ^1|-U&^SJk?{_ zMj;%>!z$(o(MiMKoTXtZyouFd1Y$tXn$#B=w5X#!fV?bKE>_M!t%mF#S||p?oB9lf z*X9ql4v0gon}BNcI;a}G9;!yOAzoIBy1JlIY;?c_C%Av{usKRWAw$OCuGZNx> zg*YD}^Ja)tLtA_)w8i&@wD=OiXMg}LHggN_{PV(9sKHo+{?lM24F)3>8ab2#^Mfv9 zR;V-h;60;*55`X~et9Mv?(Y867ZihGd+nFf5A2Vp{Tl`&Mbdw#ELy`7qVQ0IVO4DZ zN#kk%O2Q0INWLNdONcB(4aVUR$qSLh@IY%uQeH{dX$*$Z8UvH}8;nwHF&GwM8jEfW z#_PVb`~vJWan9$G+{fq~ghiRrVB9;QDUzpfOmLs-5usuBnM#;hLVj}$Mk;)s!9bv0 zaB%kNI~|N-JIcDj&+c|V!^fe{Upt)sgC4r zUc%OIxN@j+pmL34B<=u@5LW+8r+#k>kF`v%d~Qvcl|{%W5VB8>f>vv>a;9>sast}hheLJ;L)<55Z<}5RaXQ4C3E6jtTECY=|pM zT&9NfNNS86rUeT}(jw*17UuV9pgl8QIaWDZISljrG*G!TojuhKZ*7_0p4lFvJt2-6 zv}Ya%?U~0ydgcmou+;*z)XXh>pmOa8D2#bwh_L^ZkE9tv1Whx-qlEC5RAlpc!CbnR zJh#jXcBcJdC&yDr=WfgkDUyC$w`kH?6*XWhbY8G3&I?_y)6NdGy{AG>#E0zshX_8* zmQ#p?hDdCPd`rqJ={n84V6-;h~NN6InF35I5TzyoT{9t9Jf0#H?sTvuPv6w zn;L`P=V6*=p0$l&r32Kb=ZCzv50#%iAyX#Q((Fz6SW4E;PbYjNHB!H330t$V%F)W< z$|0yewZkJxr=8!PFy;^{ZzBm~3_>1oLGAn_LtfW~=+(mo?{9%QgcfxF>=!(5Q4DLF z$gM=$(dHPN9x{W$ESW+)%*5ZC^ezbpjOYIKr&%L*l0uebAx@n{qD_ZFBGJQcrW&oZ zn!J(Lp?_JV^;B+QuNQKs)vdb{o1h}{=8#WLKt<$m$f5%(B1aQGmhu&m;|U*W zSuaK8B(#1rl~a`ymE%wmnGF%G5K#*eJ*bH6O&HM(5mlH^PK1au)C4~dA}ZGbhSS_5 zpp~PQ!<9qOUXtaw;A5&D)n4(!JR&C$K%k%Wh>*9dp;EOijNj|=T6e&nxzXyC&+Q}>0PN%EZYl05PXav7vcyPqsc&L4+In`Em=-jyVS zH+|d1j6=?XF=k~MQM;PqAITUpo7lC4hillbH@q$3Tj~%n+Y!%*C^aa8GK3;1QCbNF zOe>+l>3Ez9S$KwQ6Na}IfyFUfPxw7Wpcmc~0k027X1JsdOjS-)jzfL3S*W2foxa(0 z$Z{^kK!j}ag>3RceY5MKI`MpnE<8^#5ds=(<`z0yISjMO2mPmgOqvlk`Ajo{(;3o1 zrrmVSZZ6$_xvThQ_NRu^ezuEB|8MwaDbjeeBb`-I47S6}SQ53|W;`}vC}Gw{A--IQ zZ}utia0*87oRW4S}KyAWts7)}PHsNT<0z71gG-Tf@WIBP`gx5oD!s{S5;U>ZM zD?owF+(IWR$DuZ13i?l*kTlo?Gu9?pp!;~srrp#gm`nG0R~MULe`*u#XVswe|AtL4 zu{ydZL_I>bu%I?!N@5e*#$yx462{L6u^}NgCB()PsG zNU)JdyhfoI33`FBu4XY3%mfKDGbCMcCEIgHU3%@FJL=L4ld%-!w--4wR@Q=tXA^mN z2JD<2f6dO>_NzH8_9_mF-B^8qQsu@bw00Ly@7P?qD169o>=s#66X;4iwRy8e9)$Evd zQ`Iz=?ui{mHSJGT(|%SpOaE`ErioRR*$~qbvPBM6%_WJdO&gDDP9@CD7NW`_svY88 zz|XSWa(HlC?SQa3Jcv6K_2R=9 zEIzvV+uuaP%kuAXWN6(#_jeyVcwhz*EC8d>ZS9pRiN83hl!1?2~a`p**jsKc^%e+^u(*Lr%ukusN&p z>yReoUgNyiIJYfNhS(OUZXFLuYUb@{)u8nMhM709V!1uUEQfepP*EF~h}MYl zh}>|(JUI^WutGen5DyC~itA62>@S9>S%{)Y$}8zQjhQ!Eku-U~nJ>i_Gj9>5vFOIk z-}u+Q)81(HhM7N=V$d>4PcPAYO4Sql$8F|I#lNAMKQ(=}nO{v?@i<`jQB%dtpR64a zZkYL_QLkd=m-BZq^T+*re|O}DD~la3>C$y8HK#&qX1E&d3mMmBM}MaX%;9qGVLK~% zVFbf_GOkOcQau(cXS|Et!n2I)Z1O8tlv=|Y*TeM=bI`bk*aUHMW#<;c<{?;4UG5dmn9IYI#9I_WMs%@xoHJ!$Fdx((= zd2AB0wG?6jpvLu)P~-XtILofJU=JHG_GWIOYfmohK*xN5c>if!lV*g;-n1jECqvem zaC75oF5Oo!iXJ<)KQ*rQv&Oab|Aui*k;Hc`nsiphaa)IqnpM%bo-!Wex@tT;88WYj zOw=JRKU5TthOEXzjCzQoNYyUkPGek+RwPZ{Z(K{U#kg98X)L-iu9ttM-?-8{iXA;W zOB#y_(WVKSPpdSpI?rX9d$2BZ57lMvEm`Keyxl`O_q@t7_tv`1JyMss?0+wfn0}MDCt@$Kl|^u6B3Nz2DVtd+wKJz|Pqhu-d(r)$XoW z?VR0li1SNd6PirlO-Gjg;dqe(5dCT9$Zv}S+zE?j$3>B3IsD$Ei> zp4Ek?jG=H-*bRoWgfO1Um_wEjHEhmy;W})(&^YfkE~X~L)IhuNc*qhWWHyv^;mL%1 zU1+pjX!52DKW!IYVHbY&d&(>~W3SxvEcaA*iy!EXzw?AL%e|4+!|5O7KRwNB`WA^9 zjwWr)a@%USndOEO{?^ZOi}nS&FjF~IIRUeTfLTI>EFnTHNyri+MEydR5HL#!m?Z?9 zWeFizLI66$%q?`Rauhn|2VaopNaQL`#pLKu(M4kyeKB4h~>qDvvOB2*ODc~L7%h>%${#8^wp zE9pATEN8SLxhDHwt{6#nbqT@Wrm^T|mixXh_Rn&CONbPMmPvYggyvJ~{|}ZBsrWZs zLcHl^=PV&|3rh&tJ@pfH36V%14atp3Z8YjtCbjMPyG&|h{@u5PDCyF5D>bJ=YL*;l zmypj3TH0i%RGBTtZ8`U_gs6G_JuK}Kx2h|Pl{3kbRf9as(#{$~;a*XM4QD-JJe4tr ztS4&NoYlIcu(7npd9QI%GmIgHNdI z32(Y*OS@f5o2TqQafO!lhV!4OeeU~7vXznPjwQ+9P4C6AIDI4kv3C92Xw@6*xk`6` z)s~^~R)$5n-jxS01|lWRGX+lL5$^k7)2Y@*RlMY_-qQEkrVk{^;7xy|#C(eXIM-Ut ze0zN6lUruneCVl0jE%Q0;^$SGZ>PSM#G6gzUF*gOuZ4&5z+R&@5MPIFKuKHLwuR9e z3vnevysQwbAjzksW7LYFG1^fxc{6Gwk8NI4{sAk+Ys$Ynf~&C=fjRx#=eFshbK11! zo7kq|)SahfAqF_a0EZZ0Nt;T#UYm@zO(wr^oBsN7e6;2%8=`XAUw!I9^kB(l$i<&DS#YpH4&4jNojVW`ySrAxE-IyQ$M)F5Mqy_;niW zPn`z)S^X>hzu`2PSQ$GUvXl*3+Cr`7vPAD@jmK%2Nf;+7#EOQP#SlLTs;7HGmgFIN z8R8#F$}8zQjniPXGHr5Xy7xwADYodcMVQ8-tK4@l{#?J)P|YGeyPY%&v7|Jeg@5c% zeoK`bccHP;%PYu1-;-Zcbzl7sTX+6$>!=rZy!dRNVx)FOnW*pi=$q+1`ZgT0c=LXC zkcUF7Q;2nv^sVU{yTS=oqhp<9J^J>eUoK~M*1y+VFajaQJGZvk3fPECbvF*bCFwm| zp^XoEJmk@t#V$F$8+*QC{z_-nnUY$sGT$f>o-cE=!~ow*?=ZFACPr zwcPz-BxsX9-_+~Kd{}RM9p7?qqkTusozJDo#s%2dc3RJsg=Y@87oN?B9-cqcUiiWG z!YlZ83-48G`LP@)L#i604){>CXe2HK!3FtmEx6rZ5QJA$P^k3GJ zUK>H1!$YNlt|d*o>9k-j-JRcF{M2!ip$6@cwPMJ$5ONq$-pfJpNky4ZoG>zsjC6*P zi6_HINf}12JB&JXTT{ZZ)=lLNqC4{UdEx!kuO!Gg|X! z^8Sge6k8@Ti!hBvHzx8s*NTZ;CeG$aa_?K^{v$osSSs91TbrGSXqsbh9Zm1gof*f{GiEVlRL7+D>pti# za6@>o^@{LN>yvpL1e@b|;_2lqC>q$`S|3rxOH+xzqY@M;T&Q#8ExeI$~F1F5G zZCy&Cb(ZQJms&8#rB<}cTG~2mUFJBNI84B%4pk0Ru8o!^ju5tqrn8CLLf(J~S%gBH z=zTVEo%?L!*TRFXuZ9O&Ux7C9e?gjP_D*nxY(jDS;1FI)$mUkEy}$(b=-=3Uf}_iP zszDd@iQQ5ayN)H&(ACP7%4Mk7$rCR^u)EKBPI)!TJ5^;a%yXnnaKH>WC?5=OZG8x8 zpN@yvmk@gs@+=BEIHwZEEevmMb)nT+s$8s`shono(s49AmUQ;>;e?qe!b7dukS&&w zM<>vk>6IZroCq0?8w5X`2vj^a;S2ka_xIcSB?;NEo3^oa$CY|%!Zt<4UxqVmzMkN%CCjTT3>~9Y&VVfkN@YN+wK}okt-I7 zkQZJyw9>yZ@THITHU>VEKYO+7KI_?hGOJm3%;jhG%A-i0KbR$1FT?V)dh>ijp#F-? zm3)o#0unJ(C1Q#D9D>ElnaU{`n+{JrU5WV8|5+r$JC#5#AQAfnQVNuaSA+;LR3fHB zggiv(LzDn25r-2-NkWtcTAhi?@yfBvQK)k54Ur_MMC?fzNehv@5J?P?OsGUWB}8%| z67eK~SOfad%=0r)aN`rrZzZtV2HGsbX6G)3ye}6PeYfdHt1yn3tHK-NABFf!A^wvj zpOUWAY@ivfuVV7*tK7D)w}D0^{cwBt=7;N67PnlxkV?^%X$Hw~Do1##hEqwxdlTm6 zJ}b%nR+i-U0E23OFsSy9?{e5#PGfeSJz;0+8K!d?#CF0=TOrd{$g~ABZNcWa;CJ)q z<8ekSS1VUwc6(rUdqQ@5LUwyXjB$u_8{+N3?Djy%_fE*Teq6BI18A(7Tj)~dBFt_N z^j~&+yf(t}G{l<<@lxUDZoRp5|26&ZOm2V1Sq|AMN&oik_LvP_cQ|CXC#2t@!m=z8 zl3C-C=b40Yx6-+nC>X740L;-!S_{DzziPSGq<eU?uW3Qa6zmR;~t@8HVvX5AJgK52F zxz?ZMCO&;*9m-A&d1ly(OTp+lYK~SJwS#rkcno8gFiMV^qvfbMT4mI3t)q6NjvB5v z*Q=Hf(PR~FpmOb>m2d~D-P?%QB{f~%ehpb7LZyE_=}^9qB_i|{;LnGAWgMdAeeKz%ofdF* z1sF>c%=4Kg!Dp6$WBJVRVCz{>v(yfm9$_4@gqc1=rql39%j)OK8|9GBd4&JiT^6Aq7pHZ>7X8oJZwZQ`yau4hp6+B(V5FAe!-w&$TrE8gTjmPqj zgdE)onfF7McanTcx=!OB7_B8YdE*`&>A43i_8Gt1yFYbVkxCoB*~a48rolOJD=Gcg z{kJ@tYx=SAVQ=HX*Fg92{~vE}1D|PH*Y{>%Ujh{;Xu**xM16puJvOMoSPEv!?2@gs zHPwY36_4?SC7&L9j5-TBcB{deg}kgiQD-5oU}H~qi67TCZ3hVBvdzk}8`_>4ZGqTY zeQb629v3wp3#1C}`F?-bbw7DBn}wk+oe%6c*L7d__42>||Nr%JU-$j6%2FI3Akcry zlP;P8YmI33{;K3rO2|2g5Q~?HrljkM#^~(w$a(K=xRXm#y zdbG=_GjF(DnR;rmI6EBwi$0|SttPV-lM2syx%rWADSkzP?%zrp@0m}UOFx+686vXy zEw+8%X=VJu8;guRUwj1>+5QX_dCt`?0$RsW3*)0yPkGJZ6--{Gg00Q)Ic+wL8AWZ& z%TFBZt%;TtrpnpZ0F~Q>Pw?66t+XFI2(gP0I|-RKOR_BKdYx*tooe#7Q(yMN_IWZ} zl zszUiug|UVFsKUweqY9_Wk1CuhKdNx5{HVgI#rLmR=f%6$f?rw(VcRyz)VWy5DQ%9H`aOhO|7Km?tykC(6P!qEF zzwCf}ZG_oh$h0nGItLG*Dw|95H|ZPa6Ux#tNg1m?aMD{%xEyb3pvPGE2 zqN`FiKl?jns=SkI@ewuq;v32%w>B>JwExJ>Xev^Mr#Yk@MJ|y`SQG=iL#R`2M^vZU z4zLdR4!8<)Ib?kimI2xUe?4JNH-dvP5bVzm_7xYc7Z90V1fb^)#UF5WQigXb~R zkoGv#Vn#z&G9lY^k~zXL=)e1(&3~HbygEO?I(hjp)x7w+GQgPJ9w@w)&&=~e;mtgO zd=rDpj}Pa|G%1134jH4u%SuKW+HuBKH2;E+>pBlvq85h@LZzv+c6{)L#fhOC7N_~$ zm9b%4I8@6UtmO^V^7g*kma{h~kyc69Z6{quYwsp+2caWh)eb^DkVbs>H~-_Qr3Pg^dkvZA!q?fhfkJt3n%iaB#)Azj(#U(Pb6gI3^5aE zH*SQSXbyQ46tW#6DX*lnk@?wXqiv+g`yF2?wm3eEFpWjGj_)bI&L$JdHA;Jk6)S)#R zs~oKyt{j4{q{l2#y(!AfmyM_WO9`_*6EYhM*>nrhWvCaJ3emg}O$^anNqHq*xAg)>YvCsE_X4HZ;sq?i zG#1@@f!n@}b74qwoH)%>lDj%nKeUl656#X-p!rxl|z*S zP-B%BpGhgr@oKT&dB{}2gbRG!S@5Z6pdMcn-qE=h+Fyeq+elE0J(e)r`r*Buk#IHD zatq}+VXL=0QrNBBuH1x91@^+#q;o3JB+TPRX#egc%;U(AZAa)-;Gytb=Rt@be^9XF z4UoQ>H~-=lF8u1CN#q8RnRA6GaLDc&RI{w9mTQd^lj)rntcR@3jAt3X5;E5enMz6S zGaUxd4wgo%9VTzJ{ft0PQhzUE$sfX)phjb(X)gn}kU4DVlz^ zdFGjy^ZQ|K=!=e?J2&X3GI}v%N6)Q&!GV}tj-Gq52bX__Xw|69@eY{$cI}TYWh;Q~ z%FW7kXn)9J^4Ia|PMkG&Y6Gq!+RUko3F(*%z_IJyP@8{Gct_{GP@CTfIRgmokgbF{ zniSsK*$G!W)=Qgjp!Hj+T&kR{oQB%`TFBOU$X0sD);hGq<`QOx7_t=)wfQ%NY}G?M z>{dueodI-!xm)^VD20DA?MbhLK;k% zaW~`z2JTZxpA7kAI7B!f7d-R_$jHo_<>Ln(V?(6stX-%i+KEGN>_#~d)-h^VI!4p$ z7`q{hSntO%Zih_3L%gu$KGXF%Mx%9%CT|_%b-!60<3?`5SBK1^YE?>=*z~AXC{<#q zX4U3D`-hL){N@xMOnE zv!9ew`i*oWCCesvZ*+t8vJJ45ZGidSskAoBbjbYNZhuKv>bp0quUJvKsM%8GY~?i6 z3gpFGD53dM9^K>-@Zr9~gsA@+ps%I%!Jr1)C}el#->ATJjCWhd;qk5 zla&*dW0j*&UojP8J|X55VqQ=)7*81U3o%Ei8N4jST%o?=B@lD?27n!ydyKGhsB)lk z_xVLiviOQWc~QTV9_K4exPX}nj1DNIPlVVcR7iUvHcK%IX%M>Xv3Q>V9}BVh@Se^H zw3=P+liQV>mFwn*keU!j9O9Tm95qx(TM6UHLmWF)Nbe1C{1D;1TX0|uAR{wxe&8Dp z`ieCoKNnwNdVR%8$g0@;@fAxUbLa3}XIA1XOxNctjMi6}y!93D`|aW@W^xN>J;E#Y z6)Um8&pe50vsAgX?I;`lr1ELolj$FxPRse(t&-(t@B|-3Z?m*3%rad=mOBY^!UO7o z8aY23+n?VkUSqwRoG;%zY_n9xO!f9mL=K6*Qg^Xb-Nht%s9_V8W0j-k?Vt%Sei0=! zzyE?_2;SLFuPN087{E6O21}r~cq!BnUIsISgluYuSTEENELKA}9U|axwKD~+)^O!e z)lY@CoQ(DX?OI0VH#+v}UqS zi)pFRwA4hj)LU$+IoMKj%qk+RC89E=CYPv}o%F6i|2a$~ z%?R6OrWrwz!%~sM*Z=0yyy}lXoyQ-xS65i%z^WZB{j;HrtSShxw|1H8a z7Tr3hk9~1j|Bn!-F@{KXgJx37F3~8By5oxI%9N*Qpk@*!LP?@L&M~4it@=MHCx+Sz z?=fw8^fixun|;u3JC{{NS@*QRH*@y>BlsQ<3UJ`_&o3(MvPiP^<%mQ1VVbj-+JuVx zkX6nsB2xK=6ek?0-2JK&?m}IWEMxL_e`Rq+-lwO}N1M7{O^7pn*Oda+n6H zh)u|08>o!;62>4X-Z6PFTurr{zaOjB+9sXV+^k%$T=D+-dpbMeYR7cCrmc_%zfi^9 zNSH@cAqD{*liwAd>)Z`7(7Ob`paT%BnKwIs_28JiOyuV>CY#5I}cf}n-CT50LPl=Lsj_&;T@e9LREPrd`HJ}RORCdUr70?@_53l z9qXkkpN7_NsB)lk_bW?$7pls~OwSl&I#qcv#6+N~yq9#CP>4xERr!$+lY@>qzXwt2 z-v#Khxm)^n_UX%kt$g(T!8ga?NhGa=LP|aspb6 zEN*4+uwuTCV%q7B*^YY~`eNHPlE{KSh-`?DtcX{3`X|un%n%A&2RtLg4*av zVE>6zqOa6KCZAIJet@jif!(ht>{f0=J4Ifc)Ixsb%Zi0~=f1BmV-w;w;R4SwfCKA? zp%(Iy@Q%($p%$_eGFgFINR#k|l&^*CCA`|PURuatt=}f`)^ojbrE65mR2(!3z?;b%+e%TmUP{AkKO3Z(qujA zcBXeKiYE|s#{JFI@9{kw8f5wYQ?Yi3_s_QO{pO#=w4FY*^?u*PSA1J9VSeK7#KsqL zZ}ajvhjwbyJH6SEUvI|qQ1A5S<+G>zlE)(~p_KgHPunlB_4@Tt?HFpV!flhU+^k%$ zT!HVq@ejW>bu^G4^*OO|WPm!p{mYB3m6B6o-PcHRZ;owbk&dB`3? z$esbzRJIakPa$NF0a~-A%Gt{4%1NjxEQf3aK}}&UVK$0FHjY9bz=bF*bg+0sc%gF( zWYBoM;G7mf0nJ=5U}6tYOwdv#XeQ3~PZPfQM`*(P=u^Je_*;M0``C?_ywzJSe(YM3 zTy2tXOOm6v|J@_(#eXCJndL2>ui(njbI&Ez>czh6VWDkdIF;FugA7n7V@o~hWy}3Y zkd>_6I^mxPZLD(CB#a8-`>qHd(Wj}{(FFt1g!9D%Ie)_TMfR0@H zRm5#S91!xu3i&UY_7AZ%e>3C`YIx~8`H#*c9<6tz@<|l-?(>SUyOrBe(S-+uz2g;x z?G*)PSYbEy4s*zd^=jCh74{x%g>9Vo8mF+gLgu7UVQ+*y&c{_Q8~hEM&X*-&+)dFlSAn)hYh@Lap$TDz?K8>Jgw zMcrGMlkxjWbvdK90Pzo34nf5q9uWUAuONP}C_=-Ef27`F4vIe{2Xj{Z$6+gecvNQi|=#6Otp-PYw8t@us;c=5mezr^Jzt0R|QhaYdHms!t~D!pwh zfb=#i*P+r24@hsrD@e~PO2Dwv+p2e%gVGDh!JL)e4s4}ooc9{1^wvU-96+VF95TX$ z7=}c8rt4E)qm`b?A1}Rc|IJ8GSsl6bB)Z#aOG8wXl@m}=g$G1++AE04D>m1#qME9A zn1iAU$-$fz)eLM!Wt{gKr>Mq5_V=KoIv(OFLzGw|D%16e%4kJp@>W#ub8Pz(E%a;u z^QRxXbo>*p{Ms2G2d9V4A)8vv5vsxZcJ%&?;Vwd#&luWCx>>Cu`{+u@Tq|U62->JE zi9m)^SqAEmfjZ=DcgPl*M35z2x1AOnZKF)yHtN@C)QdMy%x_*~|BB4-Z~o4I!ig~z z-u&|Ysqi2Cnp5F}J85<5vQDz*AADTLS7cFM7`dX?yjHkZ)mW;Wt(=BdL6+CN?)bvT zFYzsp1Pi>fCwOHKxVn2|$TLXj#A_yeN5|})vd$&U)H7r%8m@M%j;p&hIe$-QFI?^H zhCJX7IfDe9vhF0z8KjUQ8@hUXDC7(hoZ}3V;0zL=?>0dzs=n8}ruw7z7YfZ+{f1?s z3SQ^-_5O5paqfG9*SsG26MN<0DUaG~LwH9eKS6eKta7w+*kb4bS-yPm^t?0Gsz6!A z-S;=qnTvzZL<1iE=CTjsV#&t=7LZR_Z);tdmte6|Dm1N_gcdtgiyNri{jw6@g%&TH z^}kYGTVwFS0u1>3m5J+OzNI`9b8)S8fiC1g(ussonjbCto6=P)79UusRZ zNoNf=E7vPmV4kal>_miYEkbp|^!lBRkZE1Wxkc!6mUlsyF?U0B<1WD(3?LRW@A(13 z#}y(?U9Vyrt>O3uu8$;4q51_HqkkZ|*&rD+MKTtlWGqq1SOX=%Y~^(2B+LsqQ0p?ybb&W%vvsLj4)QIu4b2wp6M20x&5GNhtq+zB$e?_99AsQQ^-;(l5 zx^DXf%4nUq$#LSnCs3u>`T`DFo5rH6qi-$_Je^{gLnPalJWz{n^^UKU%gm)q^cu2N z1KViluMBeSuDH=?nhWB&-kYN}(h+cO?rBH%Ga#k2i@CX{_eI*o;uhwHE{oPnP}s4) zXcBK?p{TX))cp#*tLm+@RlRk-cAw7J?%WQGv)1st3G<*dWXC65t=anCy6P%h~@;Sk&`MFKND?k4l@XF79!TS5b+|s@LcmF3eTiLBsl8RGY z`I1u>TDM2qo`kP-pL75n)f_fe0=QneQn>{6pYr1OP(t&~*A^%0o%Zt(>l$tek-Qin(w#>D(Ne zNtk^bs4Yz;%n{J=eCITDq`f)h#UJQC>1!a-Hvv@L+;tM;(0~3tR{v(&^-cZ>Un$Gp z=j*@tGw*$l*1PX}CAXBl&;Rda+55B*&DbPvUnJ>g-TU;eC)oQONro3X!!_e$A+xuT zT^Wg@mvr5Bl4o>mUq0d9=MQW%HRu-&%Cg12P5WN|rylwYd!Huk-~0Tgr#U%mXTq~Z zu~ANv^`iHuG`;yiollm%&qtqGB>%KQHLXl9+PO=evve_-C_DMemCB{cS=ihAG|<2I z`LlyX|ILJ|dU=6gJQe)@DNya-65iW+E7YaWg={EE74sDFt1Lu)o&IaxVT zIR>NNA?gjI-U*}LA?h8n5DHOX=+OA;kQ398A@VxGacY3Jo4J;4V)s7FOWd4@OPD1# zE$`dd{Lv5X?|pj7TfODtQ=iuEecqfTmu<_%fIjPfkS(yIZIRtL1$7{{*geo0<5|jC z+1kCM{)zC0D~C+NoH0DGPCVunbi^xmli_Sg)H}=}8xl2a&MNzHNDFhXao%g3vy#D( z4GEZ0`Aa3Ea>#~+qyeVuTPGT=we87jZJ%(RNO+*R=Kpm9UYu;}bj!Bf$NrJo6nAu~ z{Q7O_p|wy{VT9~qgxH>>L8j{yxX}uH zSyq7$jUezF6!@M0mW6q{y>>vWe_wGv-8650@bTMg848|wgXn)kxYO1T;Z9ag!0dX+ z1HwIR48k>q0y3Ok591N8Ib_$PhRs>w&cIf<#(A%C3U@qYjtaBu5u%783kQjCP1h$} zqZRIP`8aJp{%^8Dq^u5ZuVnyzQl&R!1(4oAl?>U2kx0*UebO^p>6!fT()-h`NKaWk z@%GyPgrcom8lqaMT!J<{JRquNuOKR~*j&SkYOUU34vH!y2Xj_b8?Y6Xao%g3qM8d? zYe7Xd6XNGXw(caNGF_jjj8;@8Z$T{?&)D;tzlF;P%>P9B;e5Rt@%_VbE-^ zRRvrjbh&S|vK}|PVKHB^x?ypyOR5_d^Wmu*7T2Bc-?%ut=dH+MGWBF@KICyg$T$_^ zuO%`p>ALNt%V^tU^0qxcM0?84TKn0VPd-=-h(rVHNB(-5fu4Cr{ zQyzW#ql5MDr#h40W8<-%oPGCwPq-V`C0X-Fzwup9U7KBGg7r@5!?$vf{oWSadwUssM8nDF8Wbmhdx)%>^F z`EHSnaQiN zj6sAiCbVqJ4dmTBA+k ztt`jhK6Wt$O$4zc_m%9 z-Ci`>k<8?bWWDXhQf%2?v}eOw~{To6Ebi{mwMZaC7Ra7!kKAG9O{P2 znR9H`+Y&}v3C}MLDXjf()3uqq7A1O(9#XH3rbTrp|mla;$O`x@$5OUPwAE;B>;gBowlk2|4-{vb2X<{L4Z< zZVa*fmk8cS0+@-JYw075)rov>6G6UJO=}6`NJV+t)W`pgbYm3 zPS{KMLWg@Jvyje4@B&*@2 z8rG1!K9?}tkc4f_6y$cZK{95FWGq6-SfY}#21>>nsc5Mo5=o7*?z8|&*di6n7CPSB zVkI+Bx%+^yV(j`xG!2TiA>_K4RTm_Cd3knt^Ke1l5nqfp6w1R|La3#M~`mYTVR zuUD=>y}%~)pJtXcBaDQm8DS?dhnPZCzjaF`kJgE+ufkRunBj>N^ zjoCKKrDVaTynNgj^x9OaVU{LgH7wL}jIaf(h7{}u2X7kf(f52p#Z3XSY=UeU^qP33 zW9=jvs$rwb3=UTgRSqaGT#~%_eH7IE!lT76dFSP02Ng9T=64m){H}(Y-!&oMy;~&OO>-wDarD#&)MJamr}b0QI?8G$%G3$*8xsV zZihn>UvS`S+cqpyoB7_%v@M95Va)f&X4+KUPDpQVt%M z2~Ybxr&_=eNtE3{R?ITeC z+Jw02kO>a7Yc1BnY%sjHV|DVPb*;`e>8#FX<$C1`biCOK@l+w63)+>Ycl_B1@!ui- z96J8I3##yULlpil!2|~&b~9J(J5<7MIGq+3>oYu2!%e+Do-nhwRGw`G$n9o>WXu%F zScHC#6&Q?xWPQo1YfhxV}l+;{^ zv4%Y33t1?KobrH5<_%EE+yarz>jejW04+Cj3m>Z-h3fkR^q(Vh(u^>anP!BcFvOZo zd+18sT$-JKSw?1ssXP?2@+rdKADK<8P9G0>h!S$p2RdX-$a!|Tj7I~86K3HUa?mGa zc^snn(2;p>NTQ)38XMB-lJZKrZaXp??Z|BM{*k#9TMzn>wP`H69hrar56j5BlWftk zntgPQW_Q&o3?jOP8Wf#kD^%T@|Lm{-wfUV+K1?)fep4ZvG8H z1E^cyYHaJ~$&H5Y%B{4oB* zWH?TFgEimXqov$#<#y#Jv^;t7dVA$K z$wQfT1TNdm&GY}jlQSh?Bc{9!6<6e$tcRRnhN`q(smrpO-IWjbV~TdPp2#Nv_gmAH>Kzd`Tc ze|xx%-mtj+Exk56g4sWJnCL6D*MVxU!?qH87^)no+~ws<`$}H?el_E{JgQOGymMbQ zN;lC9CS2f4FM=<<0L}FiAs=;un(JQ31jH6$u7l8th{e0(ax7f!SS{z6Bhcz~xle9a z@@r@bubUs{+Jvh~r@8JV%nO)MbKOdq2};P;D%4!x8}d;nh?%}y@P!wE;+c8#(Mx4g zw?@DtY;ykxzaEM(%1pa;Vs)ywYdWgD#0_$`a=LO7Dq-3D6faLGTyJWHt4`P1o@o}C z4hSX(z=`}VP$9noD%`1%eHW;3XA-`Uk`?Y;!mFu~!d)h8%_b_xDn~1ap~5{Kt|pzr z9Z&c|$Mg#Kc*5+n9+w5QYH!vGlNpULb0uk|wC++k z=Map??+u0QP=`zqLPqLmlzd9MZfng(Yt1IF)_mV;?;-Sy5Vx;){P0@Ynk+N+;*Xm5 ze_(&Vvh9o7x82&eU2{i*+m)M@>+pSlP~SKtwc@_||lnhx~Li>6rVV0gDOHpXeRw|b&XDg?neYqC0$b7g- zLl%YM9UbrU3Cav~TzgZ<%k&k1vs$eYbMEac9z$$vF_T;A`4B_3HuK}tc4iy5=e7FH<~A<1Z-20Yp)t2VW9`QN$KMNt>f`Sp z%%45AX&(DJt5K&0^9Odgr&Age0$84a|H{LkZhyye`O{DM1bnlq#02-*XJeJ4mBY}! zlFhf&b+irz3D_Kx=&g)DyoD>MBM;WEkQhp;l^%4tL`rd+*_D=QOlN zLzM%SySxb=?Sif*kC`5;HJz)iL8#T5-gWn0(y^KfSxrGb@gq=A{4m5?e^0P^0OnC$ zV5PTkkq#rRDwpOQi!Njk`rR4qf*=E54S>=CL!CO zAr27g+1ElIj)!axhYX;S@=CfcdY0cFFk0O;Il9|>C|imxOF@e;jYYTm{hqP%&c_^a z>TqFczRj`uT6$?d&z#bH4!g=LSPHiJt(njMT&|7Qp<0X%)?#$H>emX?(XYuX+w?72 zGGrzTW~>t9pRr|3fM?S}cEdxq1QLJwml4~F?bTGJTdQtSv4h=uMVrFO@=9l->(weJ zDPLWms2r;twO&}Iy!b;TZq9$W44~eL#Jqq>euLmiCeS2b8uFkMD%ay7?khxSA-j%H zEkB(wyOJTR2xxVND~BovDt9?It0|5U))Y;rwjWFQ9Uar3qB90VtR=*HxX%IflOex1 z3^B=%3#QKilQeVNyan3<^vB-bf55H!XCKdy#((8yjSVIjelF!Fn3z6F!}J+P!qEI7P6WP8Ga~98ybXJ&4sMyY7MqYXN@*1 z*DF`N9~;^US*<~BXe(h>Yay$(kP#q6YoIptu8`03Ky2tP!Oky0wah$IBuODFkvls> z2{YUfzBq6V?{zSa4KzQFdGS-&e^ssaQhc&WhD?I-4m7VzlB2g@B5-Mg|8USR-TTD9 z?5OoScIgf5Hob!-=q;3J@gB_EYNeJ!rrjYfJY=FJ$)}|2wyPVX9p6k|$G7|beQ$O1 z3aaSp=KRm*h^wsHL*r;S$i$fd8ODUWO8&)UYTZR7bnOB>JO zBQO0Z|M9@4H}>94`={ngRWb*zHmXHlX?KpNNvx2=^PS@%`Gs`5B>$4G*Hoizs>$1? z-q>sEH`Jzn@E_sOe6_Mxd;dc;|Ku-N?cr39<8&dL_92Vs5RW3Mc1hQ(w$WDGwc1}uwR3zsqJDJX(rt?WP5a`1%9AGky+P`UrtOBD^9eb%5VBb=X-7%dYlqRc z!{lu{e!ka^m)3S%&kE!7BmV7Fk3IX4iE_w9Ib@Vkb944g{>`@` z>0jG(J$Ko3%)GVdU*c_0>Z zL@eg7SS&8P7%;mSVXYpSq`KHjs=vtjyj;I0&GQ+#%~dQzl>?Q#S|{;ct#$sM&N1(U zrc+FVAzN`FyBMT%>EwNGZZ$Bw7*Mf13=zxk3FfSTW}CT%Y*wyApOJ4v|7FM3Ya`5G zLym`2;L#B*2p;~7++3OqKjSm<*L7yJ_F3{$I7RIH56Dv<@f+Ns)>{wtc?;@Pt%vKx zHRI{~m4w-F4cQL}$v31Mp<~!gh;D}HYKUe_$}8!*?XHN?dRCL;S$n%8rP#77ViEDa zCC$*$XAU$IKU8)_P7|j#Npg1~iiWr>)djjrRa0EFswpmF6^4sig_A`tno{OTbrFmZ zvM5G#iW;ugY44Nr4faxDHpHny21kkVjN1^DXCe(^sul7SF!4*9bR)Yn&f?qVdbi6q zqPH%KUWr^n|{bq2@hx5gja^3S31v&~a}_?7N) zoJ`;g&%Ag)@T@bPUSM;Fq}TrN7w<=G6LI8CUsfXcfmsS5#}R8Yx_%_Z*IL}dzpp*| zymRy%GCBJ((%fb(&QK)f^4Ccl#ed2(jowefvwLxqX-NIzO@2Xrz-8Cj2S#XD8=u?i z5w(+@=Oy@H?{M00e}|oL6;1d5r#iz6H+7yj_fzRv^EvvwffF~McEvKx|8J@e}ym|CRh*&qRs!5eh4prvqG8L8=voOE={xYnO zD$H>+nxHhVV^~)Setr(eQ+p|#ni zw`I3-yK)mca_xnyN$1q8NtmZ^Q0KmrFk9at&Ivj-dnn}KCu9Wspx`5DV4jf+H0AMy zLMEnoC!V8GttWT@WD>{7^^my~)cvi5%&MmrR&$ntV~bKM`iN4Gz* znDZ8HO)cs}o;J>g>R7rV&j><}4L}{so}8auG5WLp7^7(@#uoJ*c1TIorpVwXRSEwU zfjr}vzrKIl@tuU~!=#_cpFPcQzV3dS6!n?sNs^hkJ)VM?AeCoGKlbl>&yarWQ?5A} zJ5mhpQQ{${H1}zZYf6)hOPVx}`ELGm6YGaJKQK%3?>zCk6W?{>e?RfvAK3RFujx0~ zM$c2&Yv7nl!pb02G1SRZw+kVI|WGzzX)u2e2n&O)7mym*3} zn!j~l*#$HQddmw7926FO00ubdza``YFwj1o3t8iZtocG_G*Ba3OPHF69KnKCYr1l> za-wn!YD6<3N35VmG?g$%tU``hh4e~@MnI>7uMYVD48*9e6Rg?*YGdYlr*2qA@co}* zX|Eh~gJ+(vj%^|n$4EP%`KdqE*XrCpjJyOrCOo6za|UWmAYuW?7s&?K3WFpzXt%;Y3}1^_u9>^Gk?QA*-4eJA0A))+YGR;9U5PJ<@n+o8HI1t zHqU60H?iR98Fz7FkKUlLR4)x{I^?uW$gAfe%XRr#G9rW0=ckGfxJ_?hiR3$5J4J`( zNr`0}fLgDF=R0d59yp})B=sukPP+5JrgbjHm*C550GIcU+~&~bZ6s7Y3k)5Cfdn`& zSCn?SqP5Evwc}t!PbNDF^9UeZO;$eq_Fz@aS^uQhrzo*g&*Ix_i?bit2FayZU%q?`Zau~XZ8-xB+`IBaZ{b19KurLZ) ztef`GgU;sC{L$@Y5jSy)4m*7c_l>|g`ADt`-Zb{&NXVKuWQPgr^v2{oD;eWa>Y;>L z>4dC&LMGtkmp6^gm-HDBaU9)*c@rySIVdS_H{ou(d^6hdxGVdXZ>89>e6tACSadsP zKl3^Klor|P5%f=6l4^%_%r0^CL=>K$sfynkf`(yJHIyqHv)e2;YnEGberY|m*nb5% zm7(oaIFp62Ituz=SJ_eUo}AAoNti4|Li6jT1(&ba68q){8p`X9wBPjsXX}0Q^#kMW z>8q_8k5p4Po;8K%ONSu2bPV=Ynp8W84xGq+j5e^EhRutX8ilqFn?x;z{xi;O>w|Wl$Nfg?OQmM{6OE+MsS`D`CVF@@NiPt)itLAWl38EdijdQ{jFBm z<=XYVrO937sf#TAUi3l%*)do6U7QK(rHzk=tgb^I--LKENf9Mow|!`5wDxQAYQOhA z{j1vtT6~=pf!^Qz_K(-c324AWy)rCD~=&@akSSxKgu-e)FTtz2`_Zx80{wjHXk$UAYOh2zl|&G>2;XBOwpfA)5Ysg567irgwp^v0oc`L+wBPEz4l8kUI4% z`Mw?u<=R0BR>`R%N^XTDG2+VE%4u&!g0d{OzmW$k4zk{fjJ$v(&j=2A0wwvzkTalA zNzR1KfI=Q#hnyjWN^&`29ukEdT7*_-vT~wwta218>8X&J8C24z6Xronc)l|pVn`wT zWl%}KEaV6fMA9!2oDTws(#&~q8l~KSrrj@h4bDzfE$vkM#ZGqaXQ$ibq69q1B{yBb zaEJwkSWt)sNi8VhZtDV!js?m3O}Udj3u>P<{^s8=%ZC2SHE;TZ@;oe&v}YhCu*wP# zJtS%pHF7TlI}`eh(&sylp3arKVOdgIS=06H5$q7OvLWr z^NlucJT*I?Zc~P*6Xv0f)#BbMd1ZS9ZI)^D{tFR_Mi!QyrUL zr+>M(*Rjg;)qMe#`A^&jI~^M7tj&Sbm6Med@O{5b`*a`%>c?-NK`ok}e!p&n9I|L5 zhHf?uzT_ssydSt?c`bCsax=6Vr^7Nib?0u1us+1%UH;63We+3Yk|1pL#wtfEhbxDm z%aietXICNfdZQkN#@g>g^eD(pLZ)V;O&yQR6d!hWc z5bINROOOKF*!g>|{5{lLs4kx7MECSCcbFwovH+xHaZ1V3m6EklO4iPGovl<#sXa64 zv}Um^-Cft&+I&|6Dy4x+XWWLn?WEIDc>bQUMF36r_@zB#BI$fG=Y2}a8NE{aC{#)x zfk^4Yg6~QIq_hnzY`t;?I$hp`{&T!Znh{))X-0VT7c!{YfI}B`=F&W8ro6p6OE$_w z!7863Y-X>7?S~Xe{2DiD5mrUj+<>|utKyixY&_bqlrXc{kcWODCuu{B0qXXr!m`M< zGfyXsu1m@*>ALOe&S+i4r0iSWm14{4<`>(XF>V`!lZYc!@L3VH-pu*>TcD&E)*X zY;A3crnYrbF$-GqYNzUsOZ3C_Ij;;czk;&m@X)JS8&( zrzT8&L#D!szx?-NQ!(njOi@V3Ne@cy6iDTEMs#xFj!ckF67Pgb<#qz43&@0uPRj%f zX=K8MvS$Le$m8VH?VgOy=4wtqcNA%*^sMFW?Ch8v`MeEJg#iC@7n{Vh9Y@&N015hQg zC$tW`!sgl+R;h@h+fIs%R`X5XYW|1bRemD8n_F;LA*WQ5^*9@E`*EpJ`@%_$e)9va z`}?*CdfoWAE4M5i*e}S(Sg7=zeT}Ivg)DU8+%}?nmk|{21+=yV%Wk-h&PQEntRH!q z{hcFod}qTudF7A@-QODYpAzx!yj*BpPvj>vGr&6$JG=3hX!5|B;r?6m)YXdiHBdcF zo#`X%8uvz$D_qmAkksau56EyVAz`u%&*d9V_FYNW>$_Pl+jk~!CqEzRO@8>C3JP(5 z^Ot_89JaDv>XP+r9^?zkZNL%U*6NEVxp?E^&uOX-&@%*2EWTxv;E9EQs9l~|c$-Fg zV&Ohb_PT|)Xuvlv{zJ|A#>KbkEN@)AuM1pEOeed$RWe7mDS|gF{+ud#1>FGw2ZcYUmJoJ)Gkyh2SX#p#RO>LWe7d zpdCF5{pS#wG$ZULm}Z1ACoB~?e26rc=9AN9h_t~Dk;*|GQJ&gy(G?~hB(`IZg?Rdq zrvgwNvMP4Vpz*YSAYl&c_{|*jeK%y45Mq8%McN73f)1HAhbXnAypm2u%3D1~t4JpA zA0kV!Wr(zhIEs=c=T|nbx|ZTFH{!I)knkGS#O-N&Oo`vi)gyGqdmwJlAa3u5mzR4s zNxL|^N!;1ZKbpTk13LGeZNB^HTZd|z(-GTMAKZES#K2=+2Z18MVr=TMB{pw8^ zVjQbCj8+a;4nb%0vV48utN&@)4zLOc)e8$uP6d-w;C8^XLk1D3rW^|yal-TU>q< zUG%uz{zl2|=(!KkG0CySTz~Z3dk@5;W|DnrnrBD>Q&Q+GYkxrYfoUWXN>XvN& zpLb-R&c7Gf>=kVN0@dl3key*@qo+dV(jhy^Av?=Zb(%|<0XoDECF-(V)n%!w%Ur_T z7m`m%z9Hp6)#VK#^?<0$>jjkrbnqmoHpZ-~s7={f9y@yHX6Fd+1ikEFeEVY6nZxwv zPuc2GZ`R1xU4eG!J|NQJYX1{7Owo>2j#dssMJq3k(M8RV{9G|x@1&AmK+YY(X-}Y> zUl8&J3slY{Aycc6seQ;{Rj8cD6XtnH@s=!EadQomEIb_=zj}!4F=|m&{u^w=3_nmyXm2?r#4eQC$0X4r0k@ z9ijcL{};NTAa*5b&ZpaMeRk$weKAMRt&qiEa5}X^bu~jjzmxL=Ay2nMhABz6mvr6M z42{+dw`AQP{Ii}Jw(q>$`-c6QlfrPY8DGo}!!dfk-E9~ue`mL0Fn{-U8|))AL!qJ> zirC$TEBT6;r>D|`F2L-`MHXQ8Fto?3J6N#+D8o|aY~?g`!(k2j6xnpnrj|oaT89{S z$O1fM)&iYPy$Px$Z-gkvt%7}OfO43*g-%qCL1$Bw(0|URl4gWGYtxJ{&kZ@mV%kGD z9L%Np1w`(eR5?1EQnso{k@x;=%Eb2Tc*txpWbYGdY?BhbIBq;fF`6(NIU%#JkbFao z45~+iA&;9w)=ERhv!yF3ucYg?vniw1Ba`>frb@A8Hf0f}vFNI6%@@73EW4Uyi%r(- zqhWP>w~cGk>r4Pcmggbbfi2n8L3w4{yKR;lX5SI8Hd-%9^g3Ory0_7L%7VU79i4!w zj?QdVM=MYq&=oyaPL_x>)3)bi-=VBhE;Fw^m!EYrZ@2s%^J?<<|2p&9wtvy|&C2!4 z6{vabK+Vf^n%7pyBeRgN-i7Rkh3t<(&FfuI^ST>iUUvx|EdzvO<`z0zISnU^T7;$P=A>!A8SMzG)o<#F%v%Ji_5>3Aj;LB*^Bv!^?;DpC%g7Y5-1*b#q(m6e{ zOQu5BlOgMK=rmzAVY(v3lZSYAIsaMr;n+6YM2FZm+W_ptIYs8DB;OOPzZ=XgUEsg_ zexPkubq~8->=)`4x(D~X*h3u(*2~t^K?jgQr*K&`8)1mJAePL za}NXR9G1G$(Ti=^plocgkjZ_gtEg?f#Hi3e0vt)IEFz;vPOMSWyAg z$jmKty>bQW9yX!>vfb}B+=CfsyFX-3NJS2J59ZQ*&17*8%2D^AY}Lgg?|t`>B8gw) zCPxOV;>fT8bq`iW_pod{?OaNj*+z&v32`qWlLe@ImI=;*mOaV)AY<`qZJt&#B3ck#MKyAkXB$!)4;ieG5!MUv$P z#d$G9)#2ZyGKr4?)I%ROx{|=uiRTKVlKDv zB&*)qKVT+~8rl|eRoZl};<0m;mcy9Ba~M;Yx{j@o?a7c6ve3>=XW%lL#N!e}T%?>| zad6@nk=KLuwP^YBN&$apI5YPQhdyEH@Rqs z_L_yQTzZ<<4mXNfnyr>ilSEEdPE?M0D@Gy9`N13ipVt>q5pln3Xlm${HfTLq6<>ZWQa!PQyyHrkrb7 zE`MjPvy{JkbDe3jLl}M_5N$G7t~5Ty)J(vUfwq`Z=@+fH0EpFW+Ow*J!d1&*^zg*n<8gcr%!h;i?Hco?gE*A%YU5q5mkr!xFi^I60AIoU7 z+*4Zfy%%`f=(z}yEupYl4obM9Fs-GWzoOT2lO?L6y<9rQUgQ%v?+N->Ra#OwaJF)~ za#8`|bL2&~H3picx#DxYv(IUpV1eCg!QM5<+6`)EZ-A=NRLI&bWbFp^ITowWnF}$y z5VM0;YocEmpOOrlkiy#8`5$rRHc$EkOfHiS%Kq3BythYPEcb5r;4tF*1xLJ^OUf z77tYpRPOTq1mnAYzX?M*M!YRFoe~%fnRGlmR^4v6xG9>066qvAxQr$e|uoLO$h*Q!NttZE%xTXH`_Q zEvOAx72CgNJndge_#OFXqfdjE5@t{hQRt-O_+E(Sg=k`k1DBLn(sf(MVzf@f05aktRgeUkI$dW$Nh9Lr%7XYMjhe_CBni?)FI78y;FJ0*F1T@ zpRhtIO2j5DVa+6(NbX~~&I8PFwJOg&QJz!tYh`k{;^dI2;!3dYxCguCr}!f0D%^4hBVj(<)2NRtI8BD=qt{@Z1pj_oZFSAM(TjbeZ%n7M^dR!%@2+cflFrsG~4;UrPWsxjpKM|iknGnZzF{>vzV z5VctatG*Rs?>jaVt0bpGwsJzYcA)w=Em5*@<8f?b31jmi14+oh64K8wN*SV*Axasd zlv1TkxZ655qob6v?<8m`wm3G6FpWjG=J*@++KW=On}pfw z2-*68R%g9(rE;lq7P>{U5%LN-v=`PAW|#??4~Hm8$RZi4b#D!MWgV)00__9yXdgJZ zMZ(_5(cAB$!EQpewpx5s^Mmg_xbZQQyFcT`he>q5(;+7cyoJGVGDPP>rcn}|FkPSf zHQN3*dE4I~`o+2xm|O7Z;X(JCINCR8&oP9Ogf6quc9}A8a8o1>Zi*<5IK+TM3>w;H zD+x2Qhb(i$)!HSWkByJEO_A{a#z)(%Cu{f^vw>Ll@eoT6vE&d-mdLN9>$by((e{eT zpLqCq@%R%CA6*?AIzMCvaG-LR<$&R?%K}VMHuL{iu|jX!SIIJlj+aOK19WryaA7aW z#xs2E=I`F{F-tKEMfcH$&qWIyK32Hf*DHz^FboiO!M#x{zg;W8S-D=h0`uSzI((SU z;bSYr--JAP4B43v(P!xJ!ExUh^4$;vzDw}n5uo5^uEb_5r=i2g67-+LN768Sn6V3P z3v^z*ZQ4T@+~(5!+N;a(p%5KD6s%TKguOp}m{=v53E8L!aUD>HxFk`sDdTZmlL@me z7P9^i8J0pe*PjN6E1MTL~y_a=!+hP z`l3gmQf@+4Ng=Bys4ucu*Q0|WPxQmpd^pQ$Y?DrIR<2jBK(%fszSIPnAfT5r#X{V3M;TJCUY6 z)FjQNdF9DslAC0sJQS?*DZ<`2NfWEa$3td*Au~9rNlr*ia>RJrIh-&%$RRVfkQrNu zu0u_7?;43xhA3%>N=wQs>AI~+8m+mRyx$~CvBe}U!Za4$n&g}6qm7+p%c<3xy(UWD zdBY)bNP7nnQ1S}5qd@ABBmC#0NLtk<_i0sIVYLnvYsc{C<`_fNGxUbvsib^TH4h4YjnJp_cYq zh^4(o@WLdZ@6G%&x3iK)*RA*RciDQ+-#xeEoe0Bx5Qe!b3=2^h7OF6;gl3jXA%s*C zx06bv9X3L@W21CCb5LQ7RgP8;R}Mkl&N$TVm`>pw581a3d5RjcuNz_;P+@!tR2a{O z2;*6Tqvrr&n7M`TUR~IQx}5>y{ioYWnh{oBrX68u4O!8_!`+U#G!K1KaXU+7qdb;F zj1Ic}QbtSMfJGv=Cd5sIxDBY=u`0Tq9pmZrZR6oq$iyUMhb=_cp>Ag_L@7g*G{n72 z$}8!*t=lnLmtyjMw^NEOZpR`_W6`bKnfyR~qDq`Hxa7Ul7@xlVnNd?bIf#!8R)Vy8|HLvlI31G;C5GoamQz}y-<`!aZ(CUm< z4p$CU4#1dGh&hFfaxkM@!i;huqg==+2Q{y2q2_fB#JsK+JSPQoxS4BCzG>+{)tM+- zX#!ela$D)$w$j{erMYV~DUH@yx~;VKw$j?Uyhv^7`{RUdWtW2FcI9T}I<%F(0_whi z>0(qcMwKu|6=GB&Mg?uQzyKEcATK!9thdU2{{Gy@M*FVp z0<~tY>{A4yYAgy-S+tIRFJwMQw(kDem%jZvonj8}%jTJPQ3#3lOw>23I72$MICyGt zg8!c$8m}J)u>8|WT6Kc*qa)aq(`Zv0lrG)h#!5Ts>CAn9ogG?UPVx1_fm^?QU%Se-V)JkBe#~DL8f#5`{OpDs z5CccwSzaxAVf$pFUq8*b`T1Ymf8Q`avXNICFMTiNVdcL4$d3ozegf`iabOIb(2AyM zf}E_Js2sCtj)Aaw_?9vT@>RlOBszX)MRckO78nZ!V5p`*ii$ir6X=x{pW z3-yCAJe*B0#XKIaCY__jNWz>Mf?C9}gn3vSa%Ko-EQG#k zncwHXXije12ls zvv)>o+$OJvd*3OZm0f%Rc#B}8g}*Qgq3FDjj^PGM!di#`ziI|FW*fF| z1^-ETD|n6+XPeg@ee1X8)f1dq^5U)Ff84$moMKTAbI(A@q)~NPCpWoLxl}m|H8I(o z{l30wX6i>h&5`Pu0NXkToPpgAwXL^=d}IKsj&mWKEKnO;PWVE~bq2PU@M`L#jcpLN zUelG6l@pa?P#c>GvCR-$4Y6IQjh#*y+YYgH=(#-S!P=VhVn_AsAhvcApeW{U>7$jy zl|z*SQ0d5Wcya0VJrR|@)tOcinQGy{TvZ8`xhmfV=c*DK4Dkj~2^~uqZxP}>LcB?c zcY)S%7u%KH%I(TcsD$+Y&U;L!7iz)_b;`)pDq+Uw5EF#?Hh9Rl!6B0QpkU$#5RjQS zUoh=&(V|G1LPq6^T8xuogA`V4rE;lqwsIOOLRpS2-pI=~$<7fXxwVokR)fx&SPy;% zD#KgDJ34O+&v)Jgb$T;U8Csb0{JD^gnverT;XP%(yLeB<P0uiI)?VW0j-O%>(B+ z%G7kq^mNDpqmVs5sMdL(ZtpleiSS+)-qCq!c&_sjsE7XshzB+MbDPDTG!On+`Jh^H z6r6D2qIRz)>mMdV=3CK&Vj&KBGl@q}NbkIFIAnnEejLwG$iNYnsj2yyE-tssl?Djv za!uaOmA>jPmya3jWvnSvOzs?`L`+|su~RgmCu^z6lxQ6l;GD`@u)d-~%-6Hb^|;EC zZjK|(v*6_1*pG8mh2tvIeaBTwg&ERpBR19zSw4g;A7F+lIe$g3SBP&WVMyNdNqKC$(g&W^qSwZ3$4oKZ!2VDH)LZsyr-^U?#T+qdae*Jmnvr~r=ixj2DLuZX?@EfRvlvHQ0wzP zt#1ZueSQ~GkLGtG_4&6#tO>xowP2T9dWhv{FO?>J?ymfN| zYI?12GQ?_)$NDBh_Fcj8AFfs*KPlE%D$J7`vPOY7s$M7hN)$ar-YW2ays;?oj7cd{a;Mcsfo+7Hl18DxX(XLvqaD{2f(pDD zmZgN9V?*qm(h&CI5@v5sJ~_ijsW8S(mVFAG=qpv=F%@Ex4CF-RSmmf0qrkHH^3z3u z$Fh*cSZuw12$7j!fs_7%bN@h*y%eg)FN2Ecc*yhvDx&d(FO*-Xpl?qnyqfAb$(|x? zwT3H)DhDceS=XqqBZPIhrgI8;EWA*@H;JAOCXBs=oL1mI9qlKfdin{78h>2yo6=yO zc`I-kX=1g)OjJxO;e;}U5Kf^q#!6V(F6rfV<)-(enBjqi>{fCvR~&l{yO7tdVAt5{XX@h_^Ur6pVU7xlZt+sB;`ZH30 z{^p{sYbK@0Le>U`>%oS^eFhCE3({(;eH%c{k^6j!uU)Sew>>`p;K7vJXaDPw=7s+Z zJAPadN1u1{vi%}Yw&~Wkln5=~4v`Ej`$msMRxH@VawW1LOu@NKr+QQ_`$!KFk5m5V zHRE!m4o^n-hQ$TNaKqxi?1CE?f1(K%c7vbZ1HMJEOj3JgDP&TdtS;1F1;;rM?w87+ zeT=r!`_rc514nZIbCS2lGcoH){h1adH+Bwc*b{@^yQCKTa0Ib8s z+h-|a;U~}e-#c&TzhevUe+xLdaJ%mH)Z$Oh1$Qn5?_3VvzY^SK|DRg?CDmtq@s~D0 zeW)}^-dfH?Fq?ZVWc|gO> zJZ4-u03Dx(q5piSlr$sdl&B8`Ezt9YRs@4M`pna#N1w@NYB2(RlDRZL^t^JaKw0Wx zlyN3ok?p;`K1q?-*Pz*vZYab#Lx)AH;semVL5ssvK!?dDWWgO`WFdn#blBVqvF8wb z4(V&DJty34AMzL-dzO60}Y6vFO(3|N6h;eBqlP*r1kuYvuC2^MhtX(Ii^T z2LhV^@SRvQ9k51Lx{BD|H`c`x?gir8CDSHiN)JD>8cY8KlwoL z?hk_N7H%&OJb&^bLN_knxfQ%~8{Dw?&Ygtb-3{K~1a}ov{G~lYRVSBhC^|V?IbAsk zEq*y%t!F?vDUvYO3hk?zggFQivY`a^?r#Xsb#8&q-d+!h{(gW=%pIc>%`bjOk<)1c z$|-z#l~euv+|`%9@;Tg5Zalm7l(WsZo_WfnJUJ>7A77k}wQ*P4^N#Xl>FP@#`_hBr zWulKb+h@EKXwye4hbxDy2s$pCSA2W(O(6eD#5tzjyT9&#@jg>gj6t2wUCt%9YBc%2}ve+<>a0>2!;0 zA)5^$t{bX~-lyA`gD!aA8s5=)OL(qxJ46M~0pw=x&9^-KSPuA>2G>!9i!)qQxzZu! zg!*h`QqHB#l)rN>Z90FKb7}1W5@$~QlRuCku1V^q*d{8+Do3p|Vw!>(MMAchLq?I1 zQ6yv(3Cj`Fj3R_Hia@0I62T|}5Qv#u=uqVV%qRl=mr=xPBa9*;qe#do0-spvXjmJkgXrU9WX;f2+?J){X4`B%+f4=X;-X<|R$ruq*%T+UxoaCHHgQ?sK2}+~+>` zxu0{+i_EA_-r`dl? zJ@L06pI@_YmGrp#yVWQSzkWpldp*p1*aehk7u@-JA-#A_gJxGn8lyrK$V3R0f1}$) zD{<29M}4foZKK`dx6$(cQ|Mv6s*|zYV-Qo^;AfwsZM1AdP`wQ0jOqb41EtxujaEj6 zw$Vy;9R(y) z!$}WIKxy_FpftPkX(EgbGQk?;C9FYKc?R9qSg~ghVQKc=K)Cl!0j6^Riblv)>8OVz zKxy_dpnj4X%hQ4?RGt=8sXms>Ax=kG|$KdmC{QEeCOGOVqqiz~ZR7O|tVpP_2hHNhj6KR-sur z*Q_+DXm*W5X?6`uX?7c?s22@e^}mL(ZP*j0-!Xz zUOGF?{>K+JXm(YkF=`7Sm^z`ZK!=cjBknYSH2c3A{QZ1}Mr2KbPq5_Sn1`c4sVcoI zm#R`esj3NsEIkaebqlDWta8%q+kn#SpEtP4zVIp4^*O?WoGt?lp`!rtr~c7tcGuvV zX5UKU9L-*aH{n#NCViewvuozB?Lu{HZBacz{Bxq&`=L$j>h&=1VV7v8;z6L;rF>#n zpFy{;MeOOZ@D{&igmW!~#h$Bx((G3P;ochrn9c!kOUPAetA`n&G@w;!!-U7RD4Y=r%&Dz0DS8dbWIS6Ng@;BrJ?#((4DB zJZ-Nbc!6s1<)C;JEv2ID^@_(;>lBZwP##qs6wgy%cZw%Av?v#kzGUd#`B{sj>QPwm zKT+|7HjP`Gst%CQEE}QrQ41R9=9BPtYK!x!7r)WOS8A61D+3ZXl*J-m4++*@4}IFp zMZ>?U>)Gng9p6SsgvgEu4L#0(6(y86BQXrjt1aT45Bv-qBnr1H-M%$fYKWR=nVi}DCDL$Jj?-2Zvgc( zy`ePe4TEk+k?9TPonLQ|67Po9ox@jW^PPM{u&0yl_;9#pr&vu=7s7%#bQwfl08L#8 zN?j;k>cYZqJBid|#=@uzgQyG3hq^F`x-f{k&@@C#r)@g&`z`F2VEay(qD@_py+K`A zaj!0vCUxQRm~XLpr&kw-7Iop#n?U&$sKs#%sqcTJuJ8>i8zXAELeUZ&dEoB-KeMy$ z-tBD~%*B`w=eV~>m77VbE)Dj1lXs1Nr&rH!Yq#!}D}O2N>zX!b))Qjo6K zaT>j>RZ!|s-8lDU?Z>`J`WlX0;^m@JbRUw zvT~d07_Z98%-l=9=lmou4C0ThFjU+eBRgREN(qI7{` z&`Cf|V=m-AilqqlwD&@Z4-r;V4NuK%G8!!d^=TfseyM8QEBIo zW}ULM zqoXjsv~Vk1Q77h_@FuBmfj3xcO(zpcYdlprZg7*W0oC_f6H0x!eC1#wDO&TGvIi6C z=T#HwWayEJ^p)T6G2!Yk#4Z!7Kx%pqI!AqYo6%2YK0In+{7Z!O#$sIY!)~Cu%@l4g z#YP*QZ134k0k-$-0kFMi!obHS4cJzS&f&i;Tj+D*GTVEkY1!T*?dJPxL@l9j`aSIR zFb|Z?v=yk)seBp)g9h*OP9NOa7Ir=utvz}OOW58Gl(1a`MA+UXz)lJPj1Y2Fn)9#~ zD4VGZsGmlqIdhNOwbFuO;fBej?Pg#1iC^hg^;%i#hslNNK zq>gxvzbru`y_Ul%#p2{(4zQ7o-SpLuYWF@ub%D4jN1muusutO~hL!K@ih zuVIPjRY$+FF=|JE`bEPp@8D*3GX;Aysg|WD67r_t^X<{Jzlu4d>D8wqBz1by5Yo4U zLX1528FX$J@vv86(v7|$OfWY`?_bJQKtdu!RVnXLB(2N~8&ckF&>cKd>m!A!MfrkR z%j#Isl5%AaQvR`vos{RS2>Q(60i|47K>j~Y%0ts!A2^ooE9I7JAGK8Zs6_fh5?yBh z1RMzF``J&(e0P_cgc*HD%dxMoX54uZg4I5r;%|J@X?9jX)@*iB-I&n!dfQ`*P=|#= zYM`uGBt)WK{S43Tlr6^Xg%jMyHw(S4aC9wHx{D=P>bAe4u)B%2OdcsrH>`rdAsFoF zra|%b|ArYuU|OwXI1AQ>>xG^OOXb9RaaWH7d(e>PdZS|LX%&{PVeNNV>b7ORfW-_i z`DW?U?if3JYpje_9IGyx=3wyWMST@*2T{U8@_uIFd8ht#fk>yi7Mjs^K`wwyw>CER z?ITwDGP74&)ed5t2mM{0VHjzBgySa^xetPx(A;TzFH-{_LN7 zQ#rBo66%Q`o&K$zRnOOJ?ycer82gQqVBzAokvwd(#11*H{{OOVVR^5p2j)HO@-PRK zVcQ3kVXJ&HYJ`1XWGFzJ|PqU8)H_ z8f-Y0*R2#-p%nFq!s2v~FCh#XLzSSwicKC)8t*EQTfBjQC~NjM>Q!XRrP zgBa`vgVzk7fWpXyr9*?|mz-OC$d9Iwe8_+_NUs{iQUr1l&n18+Zz{L)93Op(J2N1; zQQK6$YMI;z@?yNKaHQEHE_6}qR$VOMJeJiEjHJmJCO=D!RKI@)NQGb zaTRDmj=<`1{uqORN?ZK!@^q88X(FZ#^`$)qpM2oL#3VPiY~lO zxyo;6E1Na6z+jE4YlJ&gk{b9MGIQOTWRly*eh*FRHPSJ+47yD|8eH8LcK#C)5f1eD=71C-8DKIxp;AZC<7HY6Lw+%f3fTGBCl2+Q!> z4Mf*`Q-BT00AxnURq3dQBS6jEV?h1vJ%iF(FpLc1IX8$h1u3BgQ_4quh+KXEJg+Dg)uZJ;GcVMxi z8w>XxJE!~D#X3P(Tf*9|AR;egI@ReP4htmjjS3 zAy;K%9*zPvoJv6bG@L9C!%2v>j#7o%R3Spfu!DmyYg_$Y^sn6w;=-5C~v_vN_ST~Sd zwNAYHe4as<$M#j^d*&NR&XFQPnRAsXpPW{^VQvHx{ldhx?#$Zs>x`j7M*MMx5~6O% zDoxSwl8K0Rs=U%wiF=*@s_@!=Wr*h;>@-!F-ca%LH;fSs*%nQ`L<}!kj$nY}Z2@uo z$#$;1tm3B;T1|wKsR4QH=8~Hl7&5cSYXlD3isY&<{aO;|PPHN|*idR`=P))N|GJWE zhI`YO4tF(QI^3P<-0V@g{fe#7Cuz-inD#IMR7cN<3YK*S30r)F_a~{G=y;{*&7Z;& zv1Y^IZU(WzCz4@xSs1|-3qrEO5>17b-eTp@PJ+}dij-fJs;k?8{Ijx(Q(47n!0t61 zVY%-&dZGVIsterXg7lK|)h#F$EpeR?^yb5AG~LcuP*41+f0kJ!DJWaDjfO*~?HI#? z=+urer{KExRdD?j{rz8XFe-xSy%7)lJ?sTa!EFOd!6}~<+*X6^8Z*eSH^>W0gE$<4 zQgAl`rQmJ^qTtpEuxkte6NOxrc6pcss$=p%{iNV5PYbIA$RJ!)=!>F)8y&$n0EUJAHfm1Zc*+R;~D|J zx>!&n!NR@ffOO9z(lor0+g6{0lC~VL-O=U{`0euW$F$tg$b>6gsw=$F-K9D>_z!iF zc(CS}FsWm6bMC{n35pFahH|^#4#ge>kH+YPFc-#v^Z6#N9R3JFnrPwLo|Y2P-Z3yj z2}#$OqJX0wj(FG)G+!8SXD6BC@1&e}KqZA+G(dtfZsgP}z)rmy?=vFQz9mvXX46ODy59>pP&%hmN zv(RU*k393slo9$#4@(}72^ZcN!T6Nt+YJqR-pFSV1o$WdKy21MaC+zM?t$~~0${T+ zX$3H0@OJkO05q1B=KU~p#~3d z3-{FP&4Wa?X!tfaU4FQ1lJkk!z`fH39&k#)QF1Wr8M8y|#yzo1Duk7d1^}|C{(wnK z1l*nAVv?>1mkf$n$)JcAbJUAkE;R!sgVtDN&>9a?F+(mYF(>%Zs8&5T;$gpsy+DcK zHiP&1ivbsA_}L6VIb9=iBM|XhC(yWWm=%I5k&?4Y(~KbtGj@RcYWev1*57e4eQMy- z@J7vcO2R7>nB|)7*Cnt+&6d7&&1RNY^XcmzL%-duOLJmcm}Q&6V9c=^bYz6}Ohi!B zb_@Dh{)Xy$rddR>p84_*GzA6X>P811cQLwErGXg_(;g;(J3jS`*V#BxU?+QIdHu^# z%0%2}Dhdo{ixVT!Adf^t0MP(w+#CWl(Eyrg7(_G-A{syw4GV`CB#4HE5e=Zoo7TJo zEPFWVVF_qU9fK@&4Du=hsBxzJb1f;UrwQUE7rHq`<7_t&x%j34o&>-Us|$csPPaOa z&7>^!!dkpTGdW%7Q}Sv$kgEUt58pH)+ z5SNHSTqHod^aLnIjCnZf;fRO*K$%M8K$%L)X9LF|1IHlqf@003kys9uJ1cjDc`uk z-i;||oXNWG78ckg3M70zK$%LSK>9UOJpG=tFnf^qo2W{ZUpcgu%WZ*U>+No*mQ zAiKs~TCi>*=&E(5lB!T1Rqe-ReAVXp-=&mHe%z+385oGXNbwF1_7jkRDN>@RUH3UM zBT1S%rFAuvN}}YJkwj^xl|)4b{Y;ERne+nl7Vl29+99C5SD-d z=-1>S`j;9+MF9W1v&VCD1~AY+=;$xeq0p}ZE&89$h?aUNQ%>q3Ht1Cqv)_!uL!&tZ z<4$15S_yIQh!fYX8A}_HuyXH+gpciTDFEBy4mU8i956VdJu9`tibg+-pCDF)W>Roy zq|3lyWU=0M4QqIJUsh`1BQ&`-ZMZp2<4h=R6&|q)?_vvDFRSoeM$?$t84N))J6NQa z$*P6!UW3#l2sJKWIZR`U)^I5c`e|%absFm?R@2x|A7Rn@rka@-OzyU5!ya)*y1+Ge z`TPHLIrTdHEldB9P-;ak%^tW{*<&K!`4J69Y&UR65MC05J>v$${O$0?UuKf84n(M* zL|UWaA$0`{m~PKQtD2XfB-VLMQ^$6YtN287w7DajvEv9bRXeijVq1sXXSk}=|LX)F z@<DFDq9re7DKB{ zR898%l(GJUp+AB_KZ5k%t}5{|@(y=7+=Z0-QE}$Rl)o(Q426eE>@3^$_>yRN!{nK3 zB`A9ZC_AmHVpYYTwb|(hg>(ES2mz%>aivgPQ;cs)-CI)jva*+>^Ou~EdDq%R$aQ)& zygc>76v)?-xa%iWwq!*#+_}P>Hv80r#KD~Dz@_+P*>p8BMrhv*w9Ke|63Qcc8%yg{RgX<>HrlU!)WLrx4YrvP};%o)D%^4)uV!xf3>J#NQTw;HseW zM{ddy3v+5}t5}uxXOol_xz2QwV8wftuSds;@EpSD~ z9`>}KnxQQ&a!-3J&(%O%eIQp06n90!os4SlLkiCy*BFn6mSm@rtLStfcuZ7jozO$= zTAB#8eEe)lwsVMdM&9mRmwSlz=`?4nnnSmAsDBy7dP30^vKe<^?*H!6 z_Eox+CiT#y>Uq5OE2SHguvuLULUfwsr%j>*osg%RAU#-xzEw0$1kf2)cTOB=lhky{ zkw+o+S{T+XHuK23oTei*6~@-581V+3&)cie5>aNqD+$wi5~!aH;W1#mUMJ9?MFCBh z;|B5h8Vuex?~zC2O38_qQG;9DyGUpmv2fUc%KA*S7W*t91WSm*9JK_+aqr0wZ>7}nxV%p9l9b@ zQ}^wu6>HM`X_}o>vt6B0H~Vure+k_1koxY;?QeOIPh@F6-SEU#HHCi6xE`emJTZN! z|M`=nEfUJ}+nc@4Obzs)|9rBO*EWz_zvhd5rydr@$IBY>w2~+1W1`4Y1bbX9=^*-T zse6_rssC!5g`h1_T%Wy~F&B<$6<)A`2(>=->1y2Ssh5k<8DT-n3}-JjmKnc07oG0j zOYYin6s0a5nuuTiyV=>w2U&erZsw2A-$hGt(GTaEqozlrm6MX@oLFH*Lb}PRKXHRx zI$2@YK1gNxxK8x%uPNKC9tepe#$~rMROWt#**YQE1Y?;M7EP`7tH0mPlE`x(iTZ)3 zSNV|~Mad5A0fHv)qa9=kZONunFpMg2yk#8k2b&rD^mn_Y{d97mKLM$#S zf$7vo9U-?^q0~K_EV}r3(^LOIo!wSGiR|fVXaX)K630!hnt+gq7?CV z$O|R;F|59gj;idTkar2#e7J;*@*Zr0jRVh%pnc<}=dDo_Hf3K;9d=QMWTF45b3+Ibu>D5j&42QWO%YG#BYQ zyP0qxC&M%UM2@0Gj-nP$&Q_)%z2cy7`=_Zhw10+YlGT1itNn^v zI1O7}rE34;(Ej(=+Mn80URKxsU5_8~ya|OxQ{YEg6~YfqIdraQu_$O!)WT_T#~n;G z7Q>GpQy6|2sON_w>iIE2IPhbVXU`8siyw+wI1Pj3t~LMAUF9Phn!l`}`Bip^q`;1G z)dxFDKs1TiVNsBxs0EWED@-~G0ZFTnD3cN#5l$Zjw}R-+J|*=l2~Ulht!F9YUUYGf zknqRMN9+#i=T<^C`?QhiYQ=PEiV!2iRMZ|Zi*$uUG=($wKUp+aCkt(2(PWW)imME+ z6vlev?;;VB#|CH(+i?oeu)itjj4IZnJ=U-NN*5%!6>MJZ>53Ldnn#(1KupBdL(!>v zAz_o5n`fK;Aym@*n|Se33e3X;kDEf~f}@P$Y|`r;72S>m8P4F%DRG zoQvgm>I9GFKE2PBe*u;(5lfN~u>_iqyg&jN7L0FJY;oqTh2!ABlJRzu{2K2~4QsrU zrw$!+t_92YwpzYFYsj~#n$NW_N%NM(M}Bz`wBVPpscE~AS6=?xL} zw&&rJFr7og?&YDWhDKv$0nRMW1kD^SJKUZYw~qNuJ?;UUgZVJK1Kg(G>3 zbE_&CT^|S#9gYsTf}q; zVYgrPY5hm#(IUS)v4De-xcl&ppo|}YS5x@H*1AvAu zhG8B->Hri=;+}w?a|WX^+Wyprp|P^d$zBI+?+B}|b%!fdRaf}8>N_50SjWG;+VRIx zt7f_5r)f-`u*Hw_bt<7cK6KHdspA#CI)u&rK^rur|DJJFd5_7Nxq6J+x!efMX2!1e zsBtJGZtzDQg5pEnjrY_17x^(8$lMPH-csej37!L)e}@Cd)rMRAV_2dV=L4!mQ;Q^f zzlRov{-&#I{=%vnFa8?kUFR>A`7kubC9|qxKu!|oC9#v^#d+P}|FHTz8!89Zy zR`ufSOZ@vFr{ZjjnvOHMq)@SRs&|y;>Rh8XQH=kwC{H9_CFPG|uIe8AYfLjzPn-3J zr<{Z$qEY7C%Xwd=sn7>t&yJHwiGRkyE6TVjmn1+LCZ9Jk+XAzwbQ+U3@ zC&p%#5yo_zkSk*``8r?Y9d`9rg@!ch( z_tTZxSyhzK+sW!6yNZ669zLn2htC&*7!l6AlMvSCr5Du|UG3)br4DJL(!mtvXgc4F zl_tMwx-e%91QO4#l6a&i@#)wZNZp=A|HVnJhbg}UK5lqIJCD3=Q^2C4Gr9*S^;S=h zJ-nxRw}t}=o6Os1-+hxOPb=fR!8M4gbF;Bl?<8s|viB6f>}hLi;7eeMn?DC^drf@3$yMO@{lXAmcBT5j@!2w;;xhBj zt0}DR@IzQ3Y--qPlTjFYU{3ycbd=raljxS*d`A;=KqQYDi%*gyNb^7xtECw+>LqWpE*{Q6H*nma8iTMy~X+5 ze@tO06~MhLVw^iwK6g<_q|zFEtb_wOlRWcJ@wZE*h#% zRaHQ*l~l&nJ+3I~R@B1l4nB8SA)oub6h@{D)ccqfQQ!Vi!lC`!c;=tluV~SrsD;z8 zmE2US_BZ<6UHgO2-M7E$WEM?5bN9`!vO^>VcJ!z|*wG90K6i_P z6h$qV6z3B~f3B4&ZEgpS5<;FZ%m^-a8_=Xi(_p5}N+$FK05vJ_)?gnUsX^W>0?g)e z&y`m`?Iw6jK>MXJDV7wDadj(@ETn3I^twawMr~K!)j3OO^FoWE?_cUql0h%^OCFA? zz-C`No7jB zTX>r$x*l~@H@it?#EGabt1xWPy`F{9>smXTc6YA)M*Sv*ZB_%DgAKaf14HN9Hk+X< zl6da8O)z+?ui3&F5em;&bA8>8T{m1huX(xb^8Ye7 z`aMk=g4%#+VQ5l#Uz+COMVFEEEO}ATHX(DDvLi2I&C<7TUG@8C*k7m}G-Y~3*aOT#wkVzarfEzmv{$OnxaiONJDH@*nBUDEzd~u-vRXVLukx$utycBp)}%Q zzv9V03e+#!%nk&erwQ?~O1`WEC8#=b4$xc$K(%P8Qr*z2dHMqqesbE!N z;Q>2K5$M%gaUi>v6c7o-e?hzaWxOYvjVKD_f@IEo()o(vhtMFRCP>TOL;PM5&2qW;8~+WKaDdUQD5D~nRTfTOJG9WfL(y4 z#;XR|$wEu2?!}6BmDw&hXqys;N3=yTzWq*ZFD?9m&q0&bGnHR}(%xjL2Ao!|Ml>lI zRJXV!a)NTzbLE}pPF-T#6{WS>lmWV1fh5_=O3$H?J?ktmaW~&Alom65)EU!<(@(pj zT|b4~WxV@bduCG1Yf)ilgN#9M$j)i1Mv|o?56U0!yu(_^oZ<`6iel*|-_of=EAN6d zbY*F|O(N>i@FvP_J$x&hX5Z?sT;|&IcSz^uO~AeICPWVHOat|kkScD~WAS(K$U>0i zp#G$CLYQ*W9;UEdO>JwHnQRgf*IHgpWOEs$J3%8)PfF zLE0&Z8$k8$%+bpDkx#l^f_+ReWw5ZVjJ?5?hm4cYiZPXux-!GLmSVX{+dBPw!DaPq zmg3IW#0B?(Z2*KQ!BG!KJnRRWk^{ycz*^DeXgOM~gpKYf&PI1IN2d^UPDiIeZUc%p zg9hF48|}H)Y7m_R6dAo9<~{84FbCAe?mmO`0Z<#eW#Q>ymGbKv zsBVMKr=z}!2)}OP2(X*%@w5aYJpp-zHYp zE5P$xgIj=adYfCI**>Zy>87`Z)b;k`pO~+=YeUP+e^Qe1IXrfA)pO-8947l@CoT~m zVqcAE)aI3&_nZA^^JTw`?4E>+!5KtmI69RfzPkR+K;yd=ZMt4voH@Pl?s-&EMl1zu zD^Nd)lHztM9AAh-7oo6B@V!Nu8HmcfA8j>$}or#ZiBTQOek z_doD>LE)uSuc#|}QLSi1(J6C!=B=0GlWJ*el$NNhqE~!Hx^cF0TrHedcttIIgu+d= zFds@!RDOw*$d{LXt2|x{zgpo(YvGF&-c<`)s>lv&!?szF+0}i4>iBZC%-&>iPUYE&EAAm8g6R|EcGb9FT#o{UNxeh9FD!%Enr8c^eN_{K8ss-9=G(ZLPRm zj|VHBs1-k1#h2BJGiM&O_^$6&nRSkee;1dXZ#!RKt`>K~+**uJ@qNPM1?$=x`sBtM zs^clzAk1&7rL9sm*VL+!OFlHxRdZ@FKCSR#>v|q9DBKhtUs>p?Y_RNAL1?NWKC=!H zeRbM=o&DHj)#jc}_J+ZDPc7{%rPT|`U)IuIMp{KT`gt}T-dJteFH^8Le|a0rJ|{{c z5qJG)9;K>pPWnu8k2kWTr5K;=cT@3rhSw%MUQjrF>eV&Dudn6jS_z=OoH^mli-rb$ z>(MGBPEdos&q~}QXxoQ^tdy7=i#*O9i?`HJok!7zvH12{+Dq!t?r$iaRm*;=P$eoC z)acTUf{mQXhzD+S8IP|-x$>5!DjGkXtTyrdxpaNImiE#*wELs$Q?=};Il4YaXL^3U zunvI};A4vMiEh7?NiM+)3Rj26TT-{mdD1X+&adG=rViWw$XQm)eu_}lw|e@KYO7n6 zR<9ZVzLs|E0;>63E&J(0m8jg!r082MJEO5Tnrpqm=#Ygep-v8oD;P6{+p+3`aKq)39kB?rbJEGuL^k(iD`bm+)|!0t%N^aPt;hVQ3s# zxbBU!+gvQ;QYrE{#e&``Cu1*Qnj~Hr9E1RcDQ98q4hxf>gaR#hM2Y-lDzC68l=0FG zlmbzmic_76^IoniF#va{PvT?$t||ZoP0oTrGRGpn5L9lp<$QjcM)jqvU>&!{045jAor3yQaS=T_Ca+M5n9#?SAs!zJJH~2zG zSI3NcIO1WyhhrX&dRU+Cn+Y@fNmZ?LS6j+0W)J%GZfnEAS6ky&u69SxHVe-=OoIL0 zPyEgtf8w`on$P~8`##?t&eCbDNUkMb8~a0edAXZRnj>`;4nO~{kvJ(n+F%02>STuM zY$a%rm7pN~V-PGY3QSv~-q0apWdi!)68*7YgsyASZWKP9@tzBGg7_!V(DfOP4ZoI9 zYQ?K`g1FTl7p3l*SydXsq4|UOjz?V+S%b?{$@08dN$yArVXBk0dXW9?HU=K(A|d`c z_m-R6)R(CV$b`yKrX)z$S-5FJQTB+Z!$F5G9tD$5a8dF6IjTHh0IB(%!^`hmZO5ZK zbt_`a(mM$GO$}wife`<{6}+I$BurOfCscA z+lz~+Z-Fhc0Z=`kh-=lYoo&a2ok=#~Mv-ew;AGYL0z@DAR~9Nm{>^`jCs)0nSM$42 zk73;{4`+_BMns#zE&jE)hm44vM8hRYa2XL=4C=>-I7yGu1+%m0A$Qyv#|8sq?7aGc z@ex9PVBBPn8W_c&;RtnOovB6nBJR=^@z$ZX$rzmUIi%4k|oiJD=#W8ze)Emu; zq;WPckSK_3xoG$59@!GL5;Z9Uf;PF(Z3-f43equ7L-?tNRE-AJYw;T%>L!=%i-o~* z?hQ@oRnT}+xl)~sz=xyaFP;~rF10ZoTjA}dUfh^|0p3;LAe|-MM;^mmh=*Kdi+ft) zf4Ru10qaeQ>~>0yJ}Z0l1_@6poVt4=2xH__g3IPlnd0XaRqGxYE^L%q!*7!CK4V|0t)L+Jg5rHSk4am~O%-?m0%t zo8y0NS^|fa)6NIylB$Hjnl=szrNi?16w5Ll&9Y6{L~H0NP-LUx?qlKy0|t_#)~T;4 zgV76?zP7lqm|hW0o}8Aj#k9Zy3P(yNz8f4`42hz%c&@lv(={NfHDGsL13J`zWSq6? z>)Mo#R|JZ#DfYc6QOGz(fjk}mfH(1~CK>-7Pqx)loT{TZO5K}l9{NUn`}tBsP~cnb zXqlJ#UwWisEB63s=!r33BwAG?E~@&?)vB-HX)4jsS@!+agY>rvrSIxPtE4-+>N@$$ zbv*K&to;YIPA>A1?$EJz6iZzH+u6l6b_l2XZtEf`0ZBjGGC3XRz4$B$Y%ut7lk*(= zqw_q+8jjLcdpR3_i#kFtU&^y>WUUEmT|etv0IEbBMcCC}t0@qotXb)JrA0xk^|L<6 z>zDzhViPBfuVEY)UgGLZ$N%t*Ly?Rh;i<|&Yl}6xH+24;jLp#bIu7qB$;O9zBGU2Z zDy||=z9mxusXvcb(%7Alxjv~8yH{h2KMEox6RLqMZ1`6`wBK3|;*qbc1mdVza8E7? zJB%J8WvaQ@=DOG@Gtc=l>_L42n6qLk!k=Y?{Bax`p;X`od~5w+ce2%H3Pd^Js|qb{ zPm0!zaPdb`(h#3^JDb^*Z(33t4VAuT#u?lQD&5lIW+Y zTlr-(O&Yx5f>xXEqM<%5pN=Kur@JNgXzyEWYNuH(@$A`)vBuVlR~KDZ^@K=1 z0IO~Ls;vODa%6yZiMnnvOGcQ?o0389TQ}&8D$gr5PQ2<@61*A(>K6@Pha6nd91UNv z=8SqCokGa-C~c3%qka{EN3HQco#nJut4OJ1k9xdq&Cp|>Iicn8i%nU}Gaz9RBlx4> zhKJPfee}bICnN$Ue;tD*rsxajmq4>5!*(NekR^G%aqb+6ZWzxduo~?xc!oTputEs-zPlk! zdS$DzMU6}Dk-AI7Px4npxK7^BEqm%4jnICBHYn_30rLukdkV^EV!L9eCzZx~5O}uY zT*!t0d5s)8&HgCpM@~53A!$&LwR8+l?6?2X*k#LSSZw5)Xx5eno-bXJ1SuDd94R-~ zJz@1298K<)8k3m2$r%5imoMg8$h9)4=P>wc>8cuoM~#YbnGJis6b`F3Nl$nB;?zBl zwHJTV)KO#hyHKZI`p|v*VYYOgnC+PEy+g=u(81xBrE=gmm)AMv9aw&cB*cmQTxBIL zKZrfN;R&t!q@R)+XlV_OW|hfkz#vN|rEHQSmpV@VH+f6wnM>4%&AE*QJz-U-LEI9i zt+V_=z}F&7RX+;bri6lJra4fU(=3cI%deuI(G)D9mprP+W*{-w|I;Sj)_16OaXdwY z*Kz7i%>yM5$2=VMP`nHCgpRZI)_Yw=lrqX~WBoCVl;>DMR<@khUlp#@v9I2h<+723+1c-`*OzO#iQnT$5$ zi{Ei0^u>pNpZA5=LKH%LD6S2b1Sa73c^+;z9>!07M@F2lc*8^LP>i2`FzCG0|Lb3| z{JHMmzfm7N+x|WMd%l02JZyFzUfVjVzVm8ir%qkPpZWG8EoKiO2Gif#R}AR?IO&F0 zP<$FXl6+|ZjfAp6=fKmjn^qWUN@zb3!%@SKK|cc;yg`Q`T!f}w=ukq{P2db|#>U?M8fmUi^7t9EMZi(dc_+-U?|A{&X%%y=qtV zd{Q|-KXIZoY@gM0WsPlK6&XE&B(mu_Vn>}CUcChG%e?+q zThy7F2GogjRzO|KzjDAn$S2t9!x<0L9wt1T#_OfJ^Mv(Nng1>h4Gq;NRx}Gja$2H7 zSoP>*UaD7ft{I!`U+~0ge+W17yU?qIGE5P#)gjy-a)NhAt$Y1E ziSG}IY@6GsHOKL9`-JGpqH3UPBrmSXBe#b9YPgvPAZUC#=vMA@u%}rInM|^)a`7T*8EA+f!ez zu5p}+Qc~bRYGA;aI;R4Ei7xhHBk#b?nK~ix94y$$pt&5BRuqp=twsk^!F-KP-7^_~ z@}WhE%E!4~#S<3#Ej4iK0SNn}gA;a1AfnonvnBP0Xk7qZO`_`=-ktexUaB2I;8Fuy zG@H;!Yxo>d&CjK+PE-pQRjJw!Pj;R_v*Y4}Zt|PU*vhp~q^-!}t|U=YlRPSW4TjfU zer4^4imH_Ea?8IoZ>y8hP+L&*oe(l2$vfkvI+Ig-!R1i;C5G?Nz`$tp7HfO_?PG

nnh>h2%)nxVQf5`3fg@IeZGQTRdCC%C4XXt2|~~?RGE_wuWFjSFVrJQs)g12 z7LR4Y-cIqV&ETeNs}HvblFwjRK2ScSXAV_4);I>ySb}Wc5v(4eeiKtxf{1X6GRd=k z;8C>jD@u4~s1GuxeYFmvDvX@4;HFC{UHP^o793j%*jeg-CzlRa3fAr~{RJU}NWh<^ zURt@0BB7{GB`YzUsE&DdC#v<-c%phQF`9tOK$#rWZc(&AQO~voNm_tJs&+e9{X-sn zxU|2!On*Qg#P<8Dj_So|wLnGd&Q*)$C87e$CwTS*D_Sg9)Uze{Dlb09a=X;@0kA-_ z0D50kxwEr6U@cOKR9ZMtE4sE34s?z2?CDap=u*_PrRxElZS=92P(5aST@Z!ZF>6s0 z&e3jFHBd9EGOjBv3JMhUY$><^96_SodFxMr1)2=hpD$KKJv;gd2X+kd?Af7cu|rYM zmY6Yccy{NWV+iEefI(^=yfHF+|lkq!ZZh z0}EstsOOL(>N(U+IB+P>v*(ba#UVvKTVg&$durSW=di2X@aM2SH>%tbVSzi5YK1#p zK!&lnV^I*KsAox#bhuY=7)ZuX!avp(WG|z)SQnt$boR1Wxy&(*V)XR8u$$KR?QabniT!;rj642 zH9WJ**VLOE{H2|eiN43`^U+n0vExTewnxiKh_fd6m1_JDo2x1>;H(Nn%TTKskz9~v^tBSJ9~-jH&?8VETQ=TVs~e5D)W8v5 zp39c0y5_jqzt2t${L+`6li|E-2C=Eewz(#n!{}FxX-E+lGoDHrJBo|XKT0IBBvS@j z-3&zaDxYj1wMuMM3m9=~862<%7GB3XnMNr~{h3<6N(EiK8UUL!9$g+cyV%`ad>zH* zq9$cFKeD)GAAdKtHJEFvZ@=c+8(-|qwWKm3S_#XHpaQ|~qOnK_Qmc@|hu@-Z`~1Nw4MLs2({7^8_)%j z`Y)K9XN@~^b6WEbcw=DYJWh zI+nuv2#ZWn!Kx{$8}!RWgy|&aSwBgN@Z#H894AI>E&=@vXi<=?sD+d4 zZ}U;TW_8}n0|QOr(*CT@dq`yT{cnIeOgt6sDq3fCio2sk1@3O+*^{bhaaU0bC-o#o zyW_4In|D$eN(HbhMT|2OWNeB;B9#_w2az*KIFK{KvnNNiMCFdVaJK4*baR?D?T+@k3Dyr(uu=gyCMh&8OOYZ$1cJ+g;8u{-ai3aJDsSBtB$t9JJO)JbZL{bA zU9^&!dB}z*2S_P*;sq=ON!84(RLurb6$a;6j@9YsSxnSR{HmLbw)y%- zft7Q`vx7QAMtl(sXOUuIT4G_EFlv0jvEcbJ;=_Z+5X^K7+ny*2`^2)Je)bxq-GWR% zf(_G8iHIP96Fln&5sFqPD9X4D6q9|lgK*iBwP9Etg_G>3AJuEq&nI|5e=EG->F47l zGRQv$Fqs01b`_m7{qz$RxI4(RCsonnuA&xBYM6fV?lQR#P#8)Dm}x|e(=3{PL?Mw% zt5-KYgabLfJbQ8!EpimKaB?J{bfj9{IQ^)PX470_#Wm2rY*QPi!dh1VUX zA6TKwXm^GzEUN zszUhDCLE3*76mPeS~x9X`hg#se$JpU{4h|@4@K1TqnU8vM}}w54@HX~idr}ggESxv z_uBMRZN8s=eDka95J`a@N!15C(m+4`SQMlvYQdzq>8DH654(l%4|%274+NJ5(Qyir z+T&AumCBMxL2@am!g(N?uiK&nbkRy4^xC^0NGSp71Vxj&-lz8ZXoGX6AGETvu~OGf zQe#bR6NTktO^jYGE(@=nI=EN9HVu90i*6d~@%83`l`qgSmCy30wz_9RAgpHElH9&p z={LX-XOm{|RW$!Q8R?X6Q0dq3%;aVW4)W4lWi<;Mqyh(-t+ZSz{&F@tZbHcl>h)FS z6_4{m6TT=S?8xU^>+mBi8O;&Vp-%|%ZUOA}zS_f#hiMNJ9!?|P%2x)e6NmH` zjk`@kmo!y2aKn4G7%y5UjoLZgyHZvD#pkFJ0kRylOTvxev4=Fa%GK(wdu<$<3S~bA z(Cw<~1PO(9yFxFKt*lq7;(dMQc*|#9Wj2B~JQOn-g!2W1hxE)Egp+ zQ$%eeJhOlDO6?tN?nD|gwGEStZu|-hD-S*|}*?z-e8*bUgtUB}Bv)PTi zkvrg(w@-VTiiz2HSVbeLk-k*r6o!IQoa@imWW;r+rluB1tJNI?F> z8#}WP^Eu`__%!7I{r`zt4Cwz?syt+Qz{rdb6tDvF};z1yt1(oT5{ZTB0y(ZJ#v zntIG2@B0POM593cP*rz&c!yv#{5)ls1j$z|==ZO!V{@qV3Ytl=yj6d5I7#;217f7M zehTPEZIWieAwdalKX9Xa6~m4`MZ<|drI-4|(Oy-`A9oZ)tl&VCHp3zZJ%m7kN;b%r z2ZO%RoOoY}@aPyVc=F;*xA5fs#F`U|E<>@IK@k6BsWw}%;aE9y${8UtUD_Z$D!gFo z8Qf&t3OtAwn_H4k8>3~7jmXZSg;zK~ze?5n`IW?H+B)bZ_TO2m+tdRaRZg2P&9SeJ zkfKl)I3vUx2eK9m5v{PMjr`{dRVyU6AxGyPglF~Nf9tCkCJqg%We8JkGqxy%#~JZ} zd4zUL@_HQ`VOlo?!4J5{AiTV(Zq!|%B1sX3oJd&B>{?AsA(@QJ7dIIjb1K`W3~jWT z5QiaX(B*sw;_`&flY%*nw=Qd?^3pFl#zW_H(csIY>QIS_eEy_*KB%bgJFm{toMj@N zP22CVM z#J39^4JG60KWxHUR1S%SYJlW4$hSk}6lseemm2;Ei;?Faoo@v}NBy;&t zn~GP8RwpxzmO6ZmjvK_Vubl`({y-7U$Q&uq7Jr(L=g@?P?}N2T-ZXxa zCUD|jG6y_W>x?~J>Wmuz>60RM*c{s;fc<_*ripyBCtmS#OVXHuJ&2G^}&6% zKF|paty9_uceT@%9qPvp6zXh~YU2JHU?*l)Y+?4z0oi3%{Ap9X=XxD8;nkrf4VXoS zMpST+cRz}SKvmYy%6AKRjOxpF3Z`Z0w+Z>fYhD{^<+XoB7MRG@5~*81#Xv6%u-mY9@xL`pfey0;Qx?_rNR zmWj<)Lbd?H2A5sKDC!P4op(z|#6ryO9Ks(uaJV-e59#_CAAxzGyn}qIcEdv&)%ueB zR6Zp4_bpBox@w`NiNdQw$cBTb74t=-sA1?dCEQM8*A{)_(zPpmBNq{k7ZqM1Pn5|k zpBCU=!4Nnsd~`tBmW7WFD3D&a1QgZ5_4WU(A6%a%xTGJO?X54HTXvYIMCt5Q4B_kB`iIjOox zoy3XFwYrYzYWn~bOKCTlU$gnXB_&GSuVLP~a<|TgY!&`sG_uUIFh=Sq@qX*kZ6aYdFq>`-qcfcYIhzicF>-%=c% zTH4Fh*H|+SBIY5dY)KkupVAUvVeZwMFF2oL81OF`+-|dw=8+b{wg}Z#TqhCGL?Lk( z!`&pq_AJ8aQ1&KYRZ6YoojH9XSQflzf_E;`%L>Ie-mq9EwQ_(^UjtFgRC{d#aUmv< zbs;TGAWGpLh3W)i4gw|+K?y>bK+cm9FD5XDEP1kC%zygJs+fPr9_#0jFARh^WRkLB z4!Pk1H-~5mq+cM)%^^FemN|qQ%Tl*~*_z{QXq!XY+#K>zAK#`qr0nOAnp9b<=9qA; zR1^BSV^nm#06Ag^U3c%I;V>Jsb@h|JV?odWwa{( za!wUfZ#h^oYr;DgfZyhp^XB3=o|R~aezk7Z-14cqm@v1L6o-Zrte*Mc5^FQc+;TeN zOY;7P88-0;8T7M^`q^oZ2ZYJXeVAoT3N*{SOdn+SiD(Yxb(%w}f?4Jd{WPNjwQ0tM z!ZhQMVRbQDOCVsH(OC1-jLRFQ8KonqHqGccOfyWFwP{9y>NKN3b(&G2I?X8HrkVO7 z_|;$355bQT@-t1JJ=V`OCvLErM#5F)P;I6OO?ESl4amT5%_S;jGmRbs3;F7@n`tan zGmRdk9hsi3>$YV3Ee3;J`VbY#WYdY#lm^k*mP%D+OghV?8T_R4uyo)y!cxU!f=oIV zrmVt#(z(lOv`Z7N%d`)@r+LlL8<}|C$i!pUaKtOFb5{1K;qo5Es#=t#`ZL2G6y;)K zzF!x^(lrwVGBxmZOQCtp*t!&t&aJa(Z{oY6GH%gC-Jh2=>vO$*>KOm@zNsVp)1OTx z{Zk(TWVnLkiT!m4)=M@tCq7(1^tYIvr=Nsro@LZul3R9+o4=~YnL6DKn~z(|92df* z>n3^UvWd4XS0}g(3?1d4Gjtm6bFfju+}omcSgY1yZT7v%OKd{cG7S+K@b5A3zDQp9 zqh&F(s8)j-gBT|ZN2ebP7d6> z8eZ=~gNpF}CF!rv{{#B*!i?#(jl4i@vU1dDBerZWTAw(RP zD9$}|k&Y0DVcZ<5H4=TbdVMWQeTBPX!>P)rHiYJxl6Zka!bcsZ&-6TH$MSs57OUq} z%ksZGVtL{}Qn$F~T$6hHu4{<=7?IbcuHKbunYbo(RO!i7_pT?S)XDDSd{3s{vg^sz z3MN`GFeKCkiNeY!b#hcu*F>pXO3@Cnj9-7WYJ!+Q0f{dn8R<47X-QTF(wGPVj(Rxa zVZVpH9_Bsl@-XLNtA`m6(;g-~oQ4YFFMBxYVF?&*4nAsp9qJEhEOCDvCIaqR9TX(1g}u^55u0nRqFuCHlL(1uKQ><#k84c+XnXm&ys=DqoDoIP+P~<;a=KOW*vuSjde0JR7hNqZ@9OIg+cMwG9h+WOl#!L4O zuC~`lx4Am~y22IV@p^v^KCw6Yd*6Aysn9x=mmbTY-kQaq+#JtF{Hb9qKx#ly;_sYV zE4u4*oAb^2ZsQMpPkDC@h*Mc`|o)? z08JlrnErgfm=L8ze3LM3JTRu$I!vqgi^(pnI8B%|QVu{!}KQy#&o$by&X(J$PqG$6*AE_v^(zQjCSQB zOI8G9o2z5;;X;IUEBH!zewO1lVLf`3jD`%dC;bpV*XzFx=Zky$ zan@~&C09Gb=$M2LS{Nn3-5QSADa-#?xAABTfY)I1nsy@&s&B>X`2uz2!#vw? znMbhUZ>ynLdK|!u<4-z=j=;su9Y^tZeg)B29H8_fSNcD$ny>U`l{Tk-9Umt2`6BB%N)SCZ{l;h8Bs2-TozkP!> z|0{q4HQzMJ7lc%f{DU>W$02>=L6DkKSu3Qu1t5KyLwe*wkj_ebvN^^EV;+e@j=g^A zM!%$INd7xZX4!#o_Mu<;8uUxwKKdn(Ogc1i?@t(PetzC3lEf7q2SCS(@esdL2PO)~34+ z4{3nc?XO6}Rhxq~p=UJ6Tii;_O}+l6F%sPJPR5_viwtcI<}HrBXLA;_^q&i=6T zmJcR*9DW64_=r=zVO6B(sgGzst;*K6&;K(a5DNSfd)GGMoVHMel~?LEvjHoeJrrH1 z7EXeyih#YT77neu^V!z$BX*D(A2x2XYD$$Qse8UP^zDtCj)>mO7umNQu?B!@e}rlW zId5jQdzN)1^$y~@L_wqgyI_^UaZVfCRfAa52>Pd`D87u)pI_8>mOE zh=6)o18Q^Y`9(CD^vUb8Af04z9ucWrt9S0yp0pL8a=TlJ`_!eQg!|hPg)0^~@UFeZ znK2o!SDI^jMYCyNd`cOm_fid)fE~F{-MUNlLPSe-rzP{iz?Xb%Ykyn#Mpl!lCCtv#$Pk}(b07qzM^@E+_m8zV(JvcE4Yz;$!-zATGy9Eb_FGZ~*juJn^&|x= zN0>^iDc1I`#2-){&S>e#MpJ7vzf+a8oYi+B^(n4ZC|Vk7o~y9V_Na2Q^YpQ}pscw6 zPm?m#xceQcjx}K+o59^6cA-;SZCk;GCP5o{>rag=y(~*wSs;iN)qUoRGAD&Cv+-ui z)r68<24EaLU-}0@32vVm-$@zb<#}?iD-{Z^a?8Qyt{bDF)mdJoe2^0H$yr^Vwk(Sy zfy&iU8MQo#^<-D)<|8&?^eC$c%Ztz+NuH2to5jiRkdMK7z$|jCO=+vb#6?J6iEU95{?cAms^)3`C<{2P?DPCS91#uJs{4z3xR9ppR zCemShm-PU63fVmLUTcz09oL$`So4t`GikTjm7B)<7Aw6en*qY?V*tg{*PTgE$(;C)u3J;Fm7mR)4c>0L)UI2zu$!_~v~&ei7CJZzwYG^qmp^qM2QXU`MJ6wZ;rhyleoSGa8Iy{;?b zU%l)%v)81~J8B@iMdecWx(AIW;hI|S@;#Y)Q|UiacnL>{M4<>&^K^$iqz@{A3_yS=(nDTjcH&cOWG9;6M#@Vmzg5)m@_%bgF}hib`P7P@uGETrF14b&HMOEElX6!=J7cyN(imir79a6L(*YXo4>r6{=qARNuC%yA ziXifHKq1==_d3)RKk&gu;&wZ)Vg=Fljau^~|0g#{~S7W?u|?n4j8*cPdwOf-B1zN_5{6L%rO7;3gEc=rIr;Ltsv z*fIi%0vk8CTY@-fYRk(IcbnY$t0=YR2f9a@O&O_9Hmc(+rjVq0Mv$x)rl^HUZ?(Dx zs;Jbw{=Q^l9Uo}-jVqPXRrm!trW$rv@&3CFbKonye9@fWXNr%~r_QWf*cVjd0M3)C-IclI3Eq>Xl`I5Hx( zIxFwZ_Ij$VN+}acv2GJ=hAgNrnM5vsSR|;d;wAv)4FHZrXJNpnq&EMWzhb>Y zGEsVUUv+N~w?Zq!QzPM)w~r?wG_LU3IiL+J@G;5o)>C(mc_|$DLK)I;BY1wqiyJXXu9ijJ`>s}>n#hI$2 zvt8mSZhC2sy4d-{>U6#MI!R$|=etOR7K&-k8D9SWSIMKc{93@gU6WJST*fD%E1OMp z=axU;l|OZEc^ZPB*}o4wT;(sFxBPF~t^Owf4fRVTs^uT%ThW!}^Opa*D}UGA^71QI z%YR7awUU>R&S}3Zf9>4z%j(Ntrt+KTEq}f%f9l-w>H6}AQ+}#z-^!oKS>yj5(9rmX z!jB@v&gy8T&k!e?5a0f=30_Z~|4e#D_C^M|Li%hmrvbsbBd76)3>LDjK_YW*v|w~d zFV%cto3j0cL9${RIu_ylgpGa?5MYT5&SmL%raeKC(Jc!l`T8E$=Auv>K zd=CMsM>-i1<$OPl&oFyh=db)9Rk^8)lSAdz0ckJg{1~(S8E(%GBz)*Xk#N+91zK?~TroNg1F^ODl%%BskG3S?@89d0HhUV|J&g^Y#+Fa_^`=&`fdS+?dpv6JE`Qee zv)O(N-|o*EZ?QA0x{q5kj24JFV7&4>D1mPFwFG9;rTTJ=+nx}Xr<9uXrAokJ{LaEv z!Fs%=&>0@zQ)my5*rE5p)eyWPHSqbm;EkyPwL7F@kIHDRd>sSS+xe|jBcnkp5dHM5 zID2LcyNyELet_K~EB?{z7g5Tl{Shh_4?al;{fe9il*@mS204OUar_=9m(v8)(irH;pHvj&?*;0oXT|9! zy%>0E`2tS|ukbvr@^ivxchK=uC`Gy}>h9s&@g5@MW;VSako5I04XWrgPkKqnAblKI z(Y2EM>yJ@oq!D94{UZ8f6c}G*rx-R_AnY`uKa@}XA=%TSZRG!B@7)9Itg8I)lU^W5 z;hZX$fCxuDU@EsD@){KN1cN-4fKjSOL5+en-UFr*6oz1;h|co}_SP^05O7?r-C@7jAm=Q$@ylcMkY`~C4AXx6^2z4lsb zuYKPS5P4`wnT#yo=IRMMY_YFuWO)`z<9~gtc9T+8LPZEySS_fs8u6wMCzBqf!OV8g zd>vN>imEJo$^t#!GIK$|x*%^{TV-6^KtlbS{YBG6f&|C{m83ooyu4C;wz6h_Bsl9( z!or`ESI4epC@ahBM)*)3g}L6mY`)^J?Xxkhw~6*WzMCKJfGF$FW&i4xyFon%9l9N9 zvC+154`$YNV2HKbb}~s`Z3f+}BL3B7s+l3VN2U@yVU5ArHmP(`Icg@QP39yhG9;QI z=0yVor^E(l`-b4Cw9PnVcQokpvbj~++{>`Rufztw4jcStZ1CH$!PjDg-)}ZJ15(-G zIr8h92-5~{^NVvk3MKRNPP##-n6km9HTO5E??;j#(OO+j9f=raUmdNJR?DEc%bd~C zsDcKT~r>0OY;8TL2y4rMNq1qa@(L^|_q6*vUWyQylILB!nv0}=V zY*Qs^MQR``t7PkPm7KjfoH~zGWHDM$okZ-(ePh5iIF{jFjSO+gByb05Kkjaqm( zlo-xvBNp-n8wxKhJIhg)9BXCSvDeZ^Dbh}$c6kDNdzFUb>J1H|G)Tv0kTDeyD%QzM zb|s7Nx)jYMP){+5{|3v0o$k=kNYbNXd*hm}6V%ue|MA(ry~NB+A4#R5)=}*%4b>CM zMsi2B-;Jh|?#|{eEUG!pF^Z^et$dMWTEQE(+3R&00hP=T9-!>XPJ^tl3W8tpVVlR| z;FODl(H6%lEKnSqkYB^^Tzn~$_?ZH>`Xr6*FT_wlo-+x4=e=m8&tNyKpn+WWke>3l(*kedNkLj(B>#`sE zG|@g)@BDifME9kmuda&T^0p-B+p423OQPOi9d&LJ#X3s`Ia}(Is5Wmq+%pB&u}^lL zJyXC=s`_MsOc(oFjPy_|I|h*tzJ0~DEa$oyb-j*3`noI{MMkyZc10M+u=O3fyV9q+ zx)DW(HwbgJ79qP=6_K+DJr7nDk++Dkg%L%IxRl~6a-7!#a*O&UQ8Qmw8(BgFW`5)w zP#$P60kS<>X2zRL>%dmkK>Q%DT@@ehD=3dzqxo$ci!63DyhAZ)sKwB#csF>m<1h@kNye@yaja z%|G!T@bWX_BgH_x#X!9L@%X0+48*hj6tXWMzTa{)w4)M3GGG@FkdU5(as^~3%P}h= zJ1?}5t;{0Z{1Y0E2eRuG1KAb>+49GK_*9u}U$k;Bp!%C=nRHrIM?+RZ9r+Ww?f!#8 zqO6T{eB_Eq&nO$DTXqUJ|Ae;B0_n9%0qGV4>GH=Heu_ZPagi3XFQEN6qn#aJqn+lu zk*L?`t)O>I>n!ZeAK6+E-w&-{J2#Q+O#Z7mGj~72B<-$aDLj7W zB^tyrbr&yBYgWIxSCzJIndVTaM@o&YY$TbXCi2UM>wy^dlb$>_)WawWTz zml-Z<5ZezL4oWmAWzA}FKq-c?&sL6(E(A~S-g*tr+oIkH@#NSojY{06ffIW@jGi_Q zt=~9wP3^{^>vNY5HPu}@v@zc@pnu$)9k`x(zZ+z_{XWA_T}M<7s=VDGb48%)wG*i6 zp}a7wN6`#^x($xo0*cttVg9W)9bww|MtQN}tw8`WG~vc@#dKx)us2lVEfI+?1fW2 z0{3Y2^j}9$pHlbbZ+@R=76}aMSorcy45B13bu>@i& za~akOt?jl8`ngPl*wLc^lGh5t@C?um?VUT!WfZQFyV7Nc?^Knjs-du`Fw_q_w3rD? z<9Bc=A@+U;Jx|Y1IPli$s!B*5DiskMuDw6iq=y&2TF$AhZl4#nx!O;w5DMGx3lld( z737}1Zsr+OZx%HzGd6FfxM8fH+)nxYHf~4o@>iAV&ScMA=?b3o1($rr>Vgx6>~$(w z6ZK9RrI8ixXIH(NN;&t;(>Wv~I^DXB@*6Q#X5vQ5i?_WRB7EN*O$L1tn>L7Xpy3*1{G@traq_v$YiNLW`ny z*%Ff#w#2D9r_L;A<&q%Dey{3^X}TVCN-Ar* zw7WK$RO_iB3m3lXLlK4Vh3h~c<{lOJJjr||0+%Xx?d=Tx+l4d1g}~vJHLfHq%6t6i ziEFf`A-JdI;3r|X>ZpKPa(4JgNdQ4H`1jJZ77k)H!nhkXRu6+PbqtTZMN3E!{iojq zQp%0l+kYML4jdhWTALq!mvD*@wJ-hTE}2WEx}?^%DvmO(9bz$*M)C5;KiMwcXujgB zS=E&H)M@($gP(~XyBr2V!i4gJZ_My`>*brL)elV5it}M|&-^Iabq*%Qc*_US%M6O& zs*6CUiD^`2C)xCqd(>RG1Ai4+-ouwq6j2vSbx)NfJNYj-X!Xjy#EHIoBOX~l(4-)7 zcmbM(kd>Dj-8+d=(&@qLmrePQHZyYkRDeY+bHMZ-1<=Yn)uy{ucl4TNh4>1taZ3hP z7tc9!6E><$K1}YJ-x@bvukH9L-JdbtheZooTy8;&%la0Vl^yl%J4cJlF45vzUQupw zetmU6TRFO+EjuIogb7Drw(N~3+5KvRKvEl3U{_X8b(*ZBfm5EJ|F3v-{DtJ zXZRV(s=~7U!mGZj(aLARiQ%QKSJ^O=A(%nNf(9838QfanEg?PJcpU(aN5usXz;;+d z=CeQ*P*g=AKz=nG#rrl`R1gZ<3e<1+Q)w;{15iaYO(>N6hROySr6qd=9CTUKoHP&C zyhB|WL%sBuma=_sIojoMB;4uxIY?2acw)7?C7d@52tW`98Kfh~c)DHuxD8OwDbZ~W zJQ%n9?y&>hNMOy=g)5)Z?>X;TVUJ{kOLCk&U|`&0kNxKRB=&ogu$nQgj9OTkRbfGt zp_Y+^>?Le&zsDM-}D2E)qLR{x!+@xp$E z%R(CDi3x*#g$)r+;MY$oD7S`e%9DI|dE7@D0_3GcTn$+>%{5e;7!rR*?z;C1!Mvqi z^X2^K+z}I3(|yI7n%P$=UiIFm89|6x&77$0TU9e_3r&`+xTa<{AL67a3hgCe*^JoK7My-~e$$qmaSbEOasN>bHp^6TUpFs@Q3b&tO%U~y z_9`qs;qSDJ4Z{^k8HN)zu?z@gCy55#>SQ$VXUv1>8VKr-)+)7;TFqh`YZIAfF%Phw z6;|f@9;U2xeGH5H)|5E|Vyd{EvJ$T0pB9|gTZCLbmZJ;sLs5`-A7 z8YpY(<0fKxCqaSb-MBr$3Kq*1bf_#3UvxE=+efzE0~V?vz_lz<^o2_LhFR1DO#DvuMT1wny= z4wZrz`iA8=qpNEdg9Vxlr0fxIH@*EOsd9P>C4>hiAuvFSVcR6DZ2$hl!6YGsPIKs$06OoI|VF|X&}uZ1*ADti$8EEhud>V z!QzmD4waaXSi^UwqWm$Ggd6^2D4rV$cSKm=&b%xm+{qBe|Kg4XL6H21NRWDZ7F@ui zMlJIu1e4whPXUQGFVXT6JKW$yKg~;R7>Glr#0h063Y_K_iwQJCGn@?=vntgSs^`^L-=1lS1ZEG7EJe(WYn&AErpmf^#>~@yNrEF>neK z7zXX4je(TQ>o?G;t|l~LuoJd!7dd>#>Jzg?#(0pTgC7kBci9GyZfE7pzr%J-wDmj( zT5UZ~8$?H}80H_UiVl#vse6m=>G0B$p<7?%d3~C%)(Jj>yb>mNHf4qCvluoUnwbHIuwdUKbzxJ<-IB#P2oq#)o%3-|5bFaaxbz zJ-fnxv?L|&y7F>{0%dr26GYQmv#6lKSb-7nhXF>#wyRx}-`J zAxVuH+~to;-sX=>?kMzF_%?rB(yj7qDkRc0i&B0ssS&6Sk}`B&QlmoQB{kyQCMlti zq}mPoB9N37StY5uokzek_aA+^92K z=EY8+76L?{Cg2km9`pzGG9BQZ z1u$IEbY;`)o8Hj$#@E||qe$-Qeg)eHnz5*IMy>YI{I&&$d)-M^MT*qwg|$<(u*M0F zOgu`<)KsF=DDEqC0B1fZ!6zsjb8T>M+*lN4{2CA?c|h3i5w-~~LB?~E^}Q238Ld87 zB)FjV34UmE5@N#PX%bfki|u--kwMSWg~p%f0Tk7^(HGT_O3OkQEAzB(v6w*$z;ao=eB;H|DbIYqToji-Udh zlp0Qcr!OXNz*s7Z6IJ^5o4U15gIG3$7)v0We>ng~31qtRqD>JJw_9gqfk@ne3z-To zMaui*-Zl_NZ} zuM{Pc2N*T*HjFQ*!f4O-uN0$d=``pC8cC^?O!ngr@yc|n{{<*JW3&ck#y^ebAr%S3)BW{ zWhn|x7V{6Gl$ZHZGQN~~hT;Kp7qVrA$duUGdezkIgS9SONzcdM{;`&W(h;`3lrP)B z5Xa#LQwXPRRaKAge!hFS)e5BIb2ckriA6Brui8$uLk{0M->kr)7>-YqCBd1tXfSx1 z^7Iq4_XjuSn+hU$#b<}Fu4pF=`a~t|V+u&duL88(gt^RF$EoPORo_r6SssQ0f|MX3 zh8+yKgCLwDJy8+ARdt1*W4^SVz<{}nSK=x~9M|%W(_*4?L^A9md$s7+P(+f4qDFmD z+XcPos`BhPU0o`ps7$=CQ3Tp~8?((I7%>FcR3Nq?Jk^@*Yt`@^jFu2fg7Fc@ZK!Oz zk(UtKWN2Sg(rB@rSQ|Ae6(+S+kR%%VCH~&MX6HH-<}CQpTR11cAj6d3r)G&$h@&Ci zW4x|58oG(q+{%+J+bjVVyMt2H!IyZZF&f~RMmuCfWuIdYHqPJm*A16v_B@S%-M>Fv zi})JiY~1}6{_=ZD{Dog=IX_4SYb~l}tqn3}v05;EGZ@yU+!B`P?zC_wSC&3NDvlak z5Yt6q{JUS<=k1mqhMg^8$;8Ab@TRa0N_+_oz?pv}rrco%TM_z&%6?$V%3t>l9h9I( zGLP0hC^&ojgZ}(PJzg)toMLEFbwK^(R+zb|tCFf>mJYwGv%3bPJmRB^!$W^y@~{vJw$$njO^=cn5L|$I zdKyHf-kk0#x=?S{)-IwqADFGw8%HIU#rWVMyoxn_fi4G)eYQf4iYqAo@vjb5FKW(n zda+-aDPamYvk&4ASucjxz(m$`Jc$mL6~*W*D~eWcylBRh1Le@dj(&10%-mGgE%dms za^;<(xWynQqr!oJtuaUC!19QI$>)vM^9+55PT|M!T4K+3$O72|2)YCYx}nrjwgOFsu^ruRUGVG zT^#IQ(>z#Q+dLQ@);!pHc=KRe?O@vx#lemzH4k zn!&!pslX!H3kJsv&jga}83J1iPZ#jSL#WMCAgs>U)#QgiFFzB)QTm9m7WUn5y9uGG z9tOe&t8_3fFI)HIS-~o04hppPl`*)>-ILjHo4**t6G9YeZ%7$r4Z$F40MsD_<@ak} z^`dfso5!p`8aluBHK#0o?Q7b(i%KIDTKh7TtWuWD?~ANl`{IyTw-}IDX&O5LTR4j% z#cshbt&Z#d{eae&*Uo&#?%cZ87@C$^rY1qkk;%Hf+Adxf^a>3F72}m^1WMF)4Wd-r z@l!b}?8C6j3gdErJEUj3t79TE~F`&p!h^QL+#{YU|<#4&%7u6}~D^-;z zHC+BUxy{`8T|%uQ1?yyc2<0%dPNS9S66*wQW}QT@5(TnO&Zp{_b;4`b$spFrAl3V`nFtKO3*l&q~t!A-F`>*)aG8>3gtDW?IGxzvQ-e0tM zonZ7~11u`xw)6v04va$8A7yc=i2mqkTq32pzj#y_J{J4C*U9qj#3J-ZY+Sk7MRPwr zTz~r!&HdfA&HbH+7yCO7>u+1z+~2mQxxaOFGf(yu`#Wp;nS@tPLtN8v(-62@2e-2u zu%O~PDpL#8Pi}>o8x%?>X;?b^YE5CBp*)&wxHxw)2QpmK+=K~r2$OsEugmoyyk3f^tuKf>$OgdcF(!IZfenBggEKWN zW3h!uv`lHF0%puBHvqcOxTF@TwZ9OCCTsPrKjMN#=v~WaZ5M8jB=@Q9d%)p-0SA5= zhuRAO&A5i_8u&5XIoRY5{x|A0u)Sha52+Be#@|QaXl~B7wcQuCw{ZVXA$(A`K73G@ z?`Ln)HG{4K3pYGS692DTbV1rYlGKT; zT(i+TV2?T=14)DQ#b{s+@0E#Mk96}0yPj?6srYfy(vM0&bGtMfenjF&BltN1D5klB zAwdNf#SdFK0Y;XCBf|zc+iP%}9UH(togr^(W6>VxZLkpkf}=h8M|oi}#*hAf{fAfj z(H~|y6F(nYE8~$j^nOm@-*$tflX~ah>-Q*=6z0BDA7Up zz(+3HtsyGE8lJXxpH5S=miE|-F-gn}xs#aW&aLwN8)=f5TZAiMdkywEiP52*B=C-&ihHB-6EA=K#;r$4HKma3 zMlX&~-LNtkgu+Srp>WE=*;V)P^2e8bWO;-opTQsq$K(g$goU%dY zK@e`2AB3Y8KJJIe?(%8;&Rds9IN}i+1YwW-AndpBap#96^2c9V79qDXByY^sCRwUx z-PDlHBCIn$>7&chgJo0F_QXWBDSNBJ+Tt5i+5OX0^PORv@Tp#=U9y5`%AQUU{N2yf zU!SYi6v=F%m3oi{Y-ZG4O7+VPXvL*NYLOVRk3CRUy=wbDxfUIVJ!-<(8NHCL(;Ut| zR9)W~MsY|kZpu7Sj3uW}RdLd@dV}LWJGYE1Im?X(nUn*`y$eu2dXeq>q~F2fT20@B5NPcU0yN`z<{GLOD?Rv4AaftYqT-(<~v5PD}$p>)lj zofTtNW~D`Wo2%mv>m3`|>6Ui``wyNVs~`1tepb;4uj;Gf`%iZJeuf@}>`-1R(VoUp zHFTkHYW@g=kZStsZNgyVKyZmn&(5N_y-hI;5NLlN#EY8pnKNkeC|QdNo#b1)jr;$iqGkm-6dS65E=2}1@x@2HHK2mk5?95K}feYTG~fNo-> z2!1L>C5G*PbNjG;eQQLN-3M8a25Jm>c#foTvg<^BP*Q<0y=nGV_e=@lZ=SU~_i4#g zF?!E=$swICF%4Ck*ZKrCnG+05^!SSxX4Cjjoqf8^^{T4m$$$6EhpFc*FBj2!kp$Yx zwLUPZGF+v%Imp)sRqKho(z&k>+A2TkVp}ue)6@pJI0D?$n2CnD;M4R^N@X-Hx zJS9)8gKW-N__(Vq$iM& zx*b{ycYepIrilw6^_*{Rd~`1Uj+pP6AP$O?yIpkj=&u!HyiHn)JIliRjMkHsjq<_y z^{zxX!;EbuEQHkQN$vKenjcb~e<7)7Jt0!N7!a(KOok1GR+T}&hK^U zR!P#c(a+bNO=EDntZ_Os-Mf`cmA!z6w>?+f+MSnbZ9p8`k^=;7MvnJWL;2M-m49Pz z>bopIJKhp4awp)QleWy9gCcDwfV6p@Bmc9ffX6Y4$HEICr7Y#Eg!5#On zS+T1(C#Lj#NVWo^J_V&lvV5w4#KP+0sk3eZZN4S^ET zj=KyLIIoP_lW~zV9k^w!Gq~6(^kheE}O4pe#hxhZ&o^~oL z!k(7x`H4 zGU!&RmAr^Q8mg6L*lx7zOM8C0i3KR3&S5X~YkMB!q!|%I~2X+Vw@-Llp|`p&Cl| zP^ZoBi>%y3<#UDI@lU?alMQ1a^Xy1Z1shV_D|6GyTWpgCUCI$f-5nSKMN>8h5!9o{ zc@z-k_|ux_4=++LBixoDY9H0ER02{x6)QH483%83TgVhMVtyuE2HoXYQPz%M99DXE zso|{Ml^Ins&p{;-2RjXV6x_ftlnXf67XKzkQ{XLjdxp}@s>`$KGz-&%4Y4jYIc=pH ziHhrrPpxhHwhZizpScSGWkFAE!<^mau374HP}$t|_R?&tYJeIR4es(6k8bl9k9PQr zM+~Vg-o2+LDyETp`9w&zk0-uT?OZ1^5+l-2JyJ$wMBd)gZ!jcluJ!kTeAq7tJM7xi zoFQ>x@l(5wtMET9`=MQ6a8DSywj0jNxK*IQvPEuLg(uFZAXb$w8Qf)e9Mtwk&5sxj zB36Tl*`Qzdg~!(64XxMfb5*mrc;aYhX0>i7rE3McXN+>Vk_HsajcX`=a8y(t21OB2 zKe-iVZYh#6LKe#NNJVPw5d3cfZGO_moJY@8CsR z=Weytd3%h=ErWHj4905E9Er3h#u6EcScMy1O@o z*;H_9W&0BUtO{$2#8h!_kCwCDsoKt?93MvvEs_nS&01?1T0+-qh`60xM|$B3d3y}sE|4BY zMu*d}*&D-BI6}6Ej7;t{T>f{53>!tBJC>YRPPDUL#q*O}VdkcRh%D92(&1ND`ITOK z%r4xS!1$t80vCu@#e=nu9CkR4rej!#T622m02Rv-<>|=?FR5#RMgwkznVXzSsw|dH z)$vtQdK>H;~LmHb#a{QWXb@S+Sp7YW%FHXl?TaMB96g*uwr$XO9b1oS| zJ?(7;4e|B1f}j{7G0ePSu~bp^uBv3JWkBYibTxFmq*{S4yLh@Ndv{gBpNa@C1$$;y zXz*8H+q+nz=&Gtzf1OI@uksj{Dr~dw3*{Xw(JN#Q+31lfV0;>+Vf$4844bxsj=KIW z=|?=Tz{e0G$9tYdb7t;-DFgJojw5mWhDUWaPa8e7YwXn(72lKe-jApP0T=St-=g;V&s1ztYQexN!Cy z@W)STfwJ!@CgGXiU|wlmYkggJ`eH_bGuo3}u5!&&^PzUzN$zXcgHtu?2kIwxj~x8c zwd?VIWmWBzUbX8n=(n%VxJ2fHCR=43lk*3u?;l`?s=sFx4Tr8rdz}6wTSe(C>e?lB z9rs<4yZ9mXRTl&~Er9aq&V5uQFZ$a}I+b@BxrmU7b8A;+W_gaeMcd#mU3KSv@vrFI zwIm#Wl*Hn1J{iqWW}{FqQN$I>^|>{uWk_JhINVLG5au2N{2KOeqwK_XG8lTVeWXzNw_DrllJq$c;&qTb>a%|+FYFoY!@o%dft0#>XZ<@eqy5P$(H(^qy zP>$R)f2)uFwn&et*#sE$lY7iLp*W>>N{}6Xs;Ql~eNY9&CvD%o zSzGR^IWH)72$$2R+7*XkkB$zDk=#Ns--#t zptm?T71~Wdnf{2rHj0E#_{@PNZFf8WK_N+!Isov_i%1<<=JmtaN0>Mr^h55Mf#mT_ z$v#Axo8)7uFu7;GX3ygW^T0jLynh=qAHrk~yLjp`SgrUMJbY&75NMeGPGGNl7kk;K zBSFf%Dm)!&s?b|;$A0~R8g)}0Xu3Z3)(44Hg71x>!E`I~YqF@C+Dec?uYi!n#|pUYTzLSE7N1;kG*HXV@nM5ZKY*0VHmjAA zGsx&)5Y||7&l*0enY^36OXZgzya^;^2=&f56xkJng5j1MyMp5l4r`jyHfa94UCi}infL3!+g;uD{RVs^4bi}`eAyM z7FvzJvoob4Ni!MJfLQLJ{^sUxlhS2}pS4D{4$O0AF_FH_S=sU>Wy{dv8wY;9aqkb; zTrzY)?aPOnYil(PF9~tgiUmB~rC{w)nDpI3#v*G-klZSj92F_76^$6&;n#@Rjx@h( zS6V}8HMFRhLK5q2mq;q)H?;UAlK=c2+5>AMa;wfohu3L=T_SN{Wxoc!VO7hCoq`zNiL4uU=JoQkO_xDFTBg4D3?|Q#bdjHfI1hf2HAVyl%C386fV+z*5T=DM6d%EDN9X_}1cG|o%t;!M_p1EPEP=_$N zb>zLw%Ej%q-BSGqw2EP7(5pIgg$;7c0#bEEormrT;G?E~J=ee2RClzxRtCC6(&KVq z3yy>?kAueMXMY?>ITj7~<(1yJa%nI=S-ei!*%~s)vIY?356R1*Pu{TX=stR?gdwDh zZ)))JnW08kh^dxf`G9x7Do#O@!b}Mz!JF?>ZPL#%^dSN3S!*WJ6%#tjsm0I$KLR=n|5FSyifRR z{Iv?C5C<6`$iH{yx~ejoQkALePC-p0fPtg>UaT}#QbTx7KgxRH^u((Z$&~fL6h(d(2Q611vjsbf zO%(>dEpFwPb4XD8iig+4hrP;k%a8X;7%BW3Z!(PwMjrn&JS`FS-XcuHKku%6!dj>h@z?oHh z-p?R*O^_V|rL!FYw~_0Dx!z>aKAWEG6Pn6x$oN~5l#%PJcGm| zhWRDmJT$~_7mkZpjw3!_8ID_dhES+2T3eBiJ`W5JJ!j?h+@}nmK7PN05rE6m)K zA%rT|(&4w7`86BmQ59Sq+d7t(*^|#RV)3r4SG#4iH!E_vCm$?&C zwaeUYa95$#;BAGXK~8WOWN>PbO)7&7qJXNZDMAqbA#|zd(F!H|d8T8M!JOd2E)}+! z67_aRdb-=4jUIx#3)L)&q674CfRM>g9Q37uJ*SZ;&izUQ7-tx(450>nBdQafgJc{x zKS~dfU;?ZAs)eq1RrLxp?=?x%4pzqUX{sugPZ``*C>gx1FlKN^VbmaZ1`TrH-yqB6 zz(0oN_x#eayhC_kdAs0(+9Y+ZVEIQ@$nwe-?zsL}zgRifzs6nAgo2@iaoxQZD(hnX z#I~xweB3lMKI;ZLly`8DFzAaHd(Z-vWgX~{vBX&bR%p#&t%`&2hXuqK@`P7 z7am{oXB1IO5sTtt?rXRVeWd&}8o|V39zvtZh?CG$^z<|eE|4HqlJ!iR%605$sNhOd z`3Offys69!6XvmAl<|{WVde&*QI5`7I{axb!h9Ym$#5L0<=E&S+SJQG8%_$jyv{%SQ&-S zYBzL*l+K(3YYOp8_%!%JVF{}&Tu*mLrpvzAA$4B5jD@)GzsD_HQN+}JixuG*>gY($ zJ53l1dbU$F>w1l0@v5%ZyVND;KRVmD6IVLS^+?$dj#P*HUTJI619=?BW&5rs?Mxrt zAe9Xry9n4kbi`&P_!GF+JGqL7yjd~|yZnxYZdN`<+KTfFU9FBJ7x83isk@!o^{#Y8 z>Wo6yrgX$RTn^o{?0Ufr2b)c5OBj*}6^%c3-D0c_=11m4$s|ojICrQ`F7XY3U~Lky zqS&NVdD7thrZr_Pa{h&qmKOct-o@yc&CHxCWFQq^SJ3a5C{vx#n896zQG=`)8f3-L zAS;FjSur%oiXkxQEG;b6EB`TF#C5C&1}6FD6%4lQt+OD^+9Zj;|CJc(S`AwY2kV&E zOK(=rlcLpeywG59S0QhZiH|{MEC!ju)QWe^Xbdv(0VWjv$#`MHOhmwwY2~loIb*hl(-&t{j^y1qPJ21- zkW~5dWtV$F`LmScUr;t@(r0T-CNqSER)iXzN5-yP@`CcY|KYYFtW^c#s>UPgY~m-8 zuuOe1FZ(x{7GZkZo_?@IoPO#PCxV*-8r4NH{ZV{&_>|ySrXAl*$ij`!SVA{HtzWXp z-u^9}E}$kwg1tx@lmaDa1nMWZ!psdOB~=znhd(_?QXVxX7splt{&?LyOFnEu(1*!Ab4julYfT3+PAZ9g_OMcEPms{pStDthc3Qm($gwFwuaIAvD;*VYLtb!$=vBm_#1Eo_@`wcPz2Ewzi z1F#ALE42y_{a|scAdKR&N~o%G+{%ntrI(Zj#!UbZZWWBROIU?x$Zmse%wyXLT)9=a z5Bd*c6-GVM?Sg1PunIM>4JIX3yD+eK#Z`>9`t!pc)W^P--Xly)I(6;nUqqQ)IU4aznkoOum^1|pE@ z;(1}t$twYTR)XmKy-~KsHdGWmdsP@t*es5Bah9Gbx=O-mL&szpW~l9(R%n2~?u#_! z1#@~gd69WZ<`BskW88D@AV}{A>CDw9xf)13RU$EMM+A|tq=W8STCpO`B-iMJsw7nu zVt@8tDUeKF5Vl5Qd+>WnwJNMEDf7Z;ONyj~twK`5g{0~|$~qvDIv#+e1iYjI!Ag*l zl!6aMQm&HFAvDmnKvHl1$3vIYvPhqH2a?)zqKb4YQ%gvy)~KQ{GUz0gsR%Pk&1>NY zOjVMqFH34#DUggoHCQhx^Lt4(R!YjeaKw@#DPgOSl=32}Sg1j{ABd#B4M0)?UQ&Tz zB}hq1!G|I#S4sL)tN$XBn)t$@OKMr9?;S@{xf9Y|cUVhY()5+Zz|6%TYtcgl@%#4E zJJE4WiaGq`E(JI8Gn=U+0EBVI$QbT?)~||x3`i!F;|UB%cB*vstippC*a-{+669RD z+EwlwRh)V+wT#q=d6Zyp2JG2isH@% zsu!wWG{~tNgP0yc2sel>jH2`Mhxnb|@A9$G;r$LDgMRrDpz=w8eC&JGY?h;A;L1>q znO%|@2pwV;hYSJM$RHazf)KdhAgAjMGJ!QnyB8$CL6A;+e~6F0-{r$bd%wfSs=xU; zMkLt#qZdy=6a_ANEKLNfEQ6F)Z_HVorwq{CQ1?#%Dj#Qhji)SR8e})sAicPtrgH3m zaP_}lI`}W|*CLyL!OzF(_?o5F>jCSyFcSe=^xmeroEE)ysTK3)LS4K`hf**BLTPQ^ zAhj1n%=-;8BQ!`~BB)*msGr;uDYx$RS~#`0TGOHwpY46X4?EIRCmy z4FxhfoYec6Yu?cwhceR>J^dRNY9AWUPog4gXD$LQ4P3^?pA$9GLic9pL@OF{=uoR3 z8~@5TXb4nz)RVUzm>3Q_C+e`cZQ=Nx@7F}y@gZ^_{8qVQ<$l8eOQA?fOu9${r`3_V z$BLq`?b0RcJ7SQ@q3IYMok6#&(Nh_Q0GSi{FpI-KCwz1@UB@~T%v6M<26od^lYM4hIH0j2;lP8@H-0WkJC&O6OpDqME8SB)WQQ)=eUK$fV@I%- zTw#_dRpN(ziQ>jN4^Y&jOmT(C?Rz*TG1nrfB03NWS*;66iF}ZXUP?2iBcs7 zIf@%WP_9O{f904&qXu_HG>kMNk>k9^hLqtLW-2x3gOttW7%ECKOD<9@QJIu5h$D&X zKrucLC9(D+RzKVvB}hZGP6YwmJ#6)`=wXA0c@J|y*}EQ~et!xaG8k?^aA7^?2pZJZ zRK7@V1geZVc1Q%LJ)H8eT^9DJx^#)3VE+v(u?RM+*;)SUhIbAU47vcz~j*AE=-4F}Nvb#Ft~~ z@TZ6P%A+pM#j*S)zxFwR@xSCUWjkG5`9k0KzT)gXH;b%-oxs^;2jmw%n*q5x{`jwb z{tlnN9k@XKTxa3cu2m^vK^ss%xfNz^sw@o^t1TUVn=kouX38TLxH#6Q@W=bJR(0$8 z+)49vM_0AVx~IHDF#VvbdL*P2SOmI$!}{j1A0TodV@%M8$vyM*`Q#z?)Udh{Bvun@ z)ezid@6?!uL4e{{IIJFd0Q$(PETLp`6DHIlOzuCyuzEXWSIn0*to}HGepqeJM7Cx~j?>{F3vAU}+rGt50fn_9&IHWQT3? zv>+$tD-P}|M8IY~39-@OZH1Q^+*P;)IJmXYBCxG+DL@~PxXd8mdj(2<+kux3B|VX6 z+lYrfKz)wnc3{|rDE5yRK5u?^M7w`m;dAC?xdGkk=h_Kg-{a}iD1VPvFTH59OdoFg zJD^^8*$Hf6f;0+@26oujz*>MRyoGM3-@^O+Gi3o{!U@kJ2$!$o377Xx!1Hs}!mAaz zUW)w1cWZKSP-P7|9iRSMP{pz}aLKKyEV({rO94ZssNEu(X`^76_jlPx6c1(#%Wv~M z^kruYJALIlfHOO!vPom}%c7{pG{UP|qlkaptF-=#2wZIUh}s0fq0YV96u(t3*%8ls zg(^PbP#H_B#}XNqajTwtR=f9d_*A0pqzOzhq?jsK_Q(8{`}gtLy{ZSh%f6Hb;LJP0 zS>82p$|bQjqGSt5^fC%ftGYQ=KIzrk4#`K!B0x_NOK6!%3-q^L!0bA{&bs)z>&r!Z z1y5>?k*bGm-?!#e6MCLHpnh^I%-lk$c(PbJ{CP>z_pQn!Nx3*Ew6r`R;PYfGe%xNO zQwfF282p#9aO} zUe9@%%dg^A2C-ZQ znIs!zSO}D5Q+g^uA8xmPEt~JFl|)5b7P<&SLD7bCL0{dL^wpm)Ew-6;%X3RGf>VA`5-%`l)x4BS1)U2lp9!OZdM5na0RkO@j(O^Pp0>>uzPMh?YW| z71U}WZFj+&O!Sx0qmM%n71$T=e~DW&5@9{vHJL3JS<2SzYaZB~J!^0Fqjqtj>+F`H zHTG7r(p7agdrl`T&X&|^ZQ`K`s#6qER)ixAMme<6z+9vkdU>7q8;{3VQ2426QiMH3Ou?1lc{rDcd|)M1G;-zGd7rdA7RqSWtkDmj*-BS!VSoS#YkJn z0Hq{$j3fpFHxY%DN|uXjJ3y3k98i|eF=hGS4D!Jl9Xp6(X&8 z9a@unGXGvy%byG3&-xwqzyc1&H57@%J$ZnG7GfbZt(`dTqT@>BNWJ#OxeNW;9}y>%TcbgI7ajY);V%r6YH~(&S1ND0nC-=9U>ee<~0;_wQPiqif2$#k@;ATe!$=anui##!8hBtoM z8>Op0-7xDH#A#1p)Y5u?@7`90?NpfiEPc(ECgrja>S(D^Ju_*!RdiXoZ0?nL`mly< z?#;#Q`Zw|KwGG+zugGWDZ_H)a*XVANFOD^-98_m>3!h46yf+3xTDuuFND&6N`f(tw z06!~V?*V8-4Z!LAd-xlg#xf$%x|0q3>OIbSUq-zzvp&#pn;&B`6`&MNB^nH}Ze);Z z2-2sv0#ld7U9eBac+;{m-ZV6oby55xS+s@K(I`SCs@pIp~Tbrwlyf7X4#j42E z<*01m9aUxaq;u|~kaU^1q$3}HN@b*&#Lp3wJ+?A(Fdg~Vl}z0Ik#yv5*H`BJ^K|5! zs?b(^YVYi-oZHel-(AJNicj(VO%*{h4E$VGc2t$w=^|I#E4N=`r7X>%u5{!_=}ps- zThftVtJJRD*QFz8s&cYwqMs}C=&CaHL`xF+%qq0K>B!5f&<>{~^_9~!wOR}3=gNF% zRpk3z>QKbVf3tRm<`>By6-A~i7QcbK7lNtJ3o)kPj|ksE2oX=?v09Vttf zj_gZEo{K$AM}9aRc~e!+kEJ7TuZjebeoaQuzN(h38%IgxUsM`{-RGqvYpQZyla3s% zV#2lQ$Y)VVx+?EYM_ym0@!jdjd#c*X`_qw^RW*m3Dk7__$<66V-2_e(G?b3~OjXX2 zbY!urp0}nWkE*n=yFZ$aJhQ5v?{JasQ<|Kyt03qG7x_{X*=aC-<|$NGoya6H$soHt z0#SEi&v#6NiB42NgPH6dbG<*UiEqiV?sLKuLt$?kB$!?(OkYPV%MjxqFcBS_8C(T= zDv`})LH5Drk=c9ILa`*KzZIs&^+tq)E;{xkc! z(p@0P#M9@1>lueY#D8k&qXG;^UO09L$M;d*Wh#>5tWD~L<*uSIyiOQ)7>0w@=&H6A z)aYp8YF(b1gsqFz=)neXtXm$((lzQAh6gbP8s4etrDdcMA9dfnw^X~CG26e%ExE~& ziEn((3Tjy|{BJk>2W|05Eq$5UNebkwG~z>!&VerERq#V+gY&rMai(f*gIBOcSv#yX zh0{bUhZF5hLb?~T7Z{OHWL0*mI{51UJ|KN!g{!&HX%RmHLngk42oSQAc@Ryu1+tPA z%>o&_+l~eo!V0iLTn#V$ImA5Ih&d?NQZX-cORKsI zA(KKJ556)(vHygPUZ#PhtVPm5aBwhAsCvYZ9-+F{Q280akWC6iVTz&WzbeJhwiHA2 z>4!hDVpE#L|opIe4llUh?~87lG)X>*|# zM^28^k0BTyOsq#Wb>&1jmlTyg4Ad-Z|U}ernAIvI}7bjYDJ$-j(6c3dnw*$iAMV?P2Y& z8H@H13K}#q*q3CC!xuALkINz;&jT~G&%{k9N&bg|MPKKb2Eo9-bH3?t{NbtK_!a1u zqaCmjkB-<=p& z48_k_FP`}hv!La;lCrJZVpMIMfQq7IRA_E}VrUSM;kZ&bE_-4)QluO!G>1PiG=Y?F zoB)n{nQ}e>t^^#z!lBb_f5cexvD3lv0&pzT=ob-A{CW+j4lv;k<%gD5k+{4vEs0ib zC&jOQlfpQkU~%+Zz?r`{oUS8Ysx>duhOViDwyO>e&%!ozy0AQL1y~N&hJMPD?aY4W z{mY3uL9(C?eOf3!wgMCtoL|s}t`r(QLbx0yi=YWgS0@}#S^WH$)eyH|j$n$rwYukuux%{o_zPJKd%1{^(x=HX*O z^L@tn%T;a>en;7_rA*GJglXFo#uS8ih0t6I8l9P{>?2)k3oSS`;0(4{r-))}rPI+s zt(_-sn&`=3n*U3!C*a zn>cG6%|3{wN!WlBjkdQYle67nMCw~pn zn*e)$SkvE9IIDka;c$F~8cH~uoub2(gk7-1OqDqy!d6%Ya~8k2`LP3>Txbjj*{Gd{ zwo@LKJRAe+fSb6WUo`Oj_$Ms5s$+xdI(23{E!^*mLFS13{+ zpBMV9934g3IPjhL3q^f1TgNIW?WY&7<}B}~!dW`Ezzd7b;+MXHH?R*LogEE)TRi04 zfqOY#*9&_Ld4M7O2EOozJeWb>pg&!O?xvpq=6kRoyXOM^z1K9dEq77&j_)<~{u;(b1rjH;t-KUNz%p$tH)Juub;y6EbwEVoWxrS7;M@Ws<#k=z>$f`sPjg zG~>rg8wY-P>A)*$o9;)%G|cQBlTBP6ir>Ira9)u>JFn>BC@>lh$D zhW9;wU~$IwlCn&-F_v>j*$vX+`)R*U*jP$aoMAJWYC$QsHtVBt*4VIxQY_kmvXhv0 zpi@jB>To2gIij*1Y2xO-{P+R(EpEPV9$M4{nyJ{$54SNGSVI4;Dq@g3_ofBOS6k|B zg~;Gm+rmb_4y({2J#<=C**?Elj>T3^_*n-uVsUGO{4IPYOMI7GI%0d+sp?nlZlhJU z+l}PveGbD627_SGHoHn{1O~}j7?M+1nmPY+AEpegD-s^;KY5g0>lgaTzD?tf-`dNk zv%n#bwA*@cv|R~h{P!F553)kXq@}CCJ*FUVmw*Ac!s4%h+p~S#fA^&FD=sqTJC&P) zMh&_}GK8?ILI`|p9_rL%$aeZV^^imDqSM264_iGf0wn}j1v+N=b<{+^CW$3d2$i-N z7-oKGYEwAE2$&iSQ(%IBPG$Ee9H!?gWuwPrDZylZFm-rLfwsKIH0i_54<+@MA8{K* z4SamJL03oBMHGqh9zl5C4^)j*3x%l6RVYd>jaPxz)U*VFI!Exs&|AhRca}>#plX<(5p$!47VRtl? zw(gUb!S^(fJnH>Zg79xYFert>q!bGCO3^K)VEe=TByb|ACHACFZ4k3|}?r z;f6XN%ib_0b)B-(y?I5go!|P`vdy(4h9tJ5g~%r_v9aAOsLur06C;}-Mu+D9aKN#= z4!3@&t%B8N6r@6HfuNCkE7(DLQ;XK{6!LR3A7TU|s_Sf#{D7p(^0wgMnzhqcZPFRL z$MJDIKwyqz|3=84S*GkATP>JT_M_jS%z4XSixHA*VDdcC`ShuCCLtsyM7*2>ik*;b zLC~h4m6QBUYQW@C6ho>T>kjS5;hsXEi?xLd5D_^*>0N(G+ph{@h6b{jkW<3#$x*P#QP9fC*-mN%samfJkJh{+$h&v?l)cl3i7TR^_lGY&LQ>nQ z0-$3|d7RWNh_V&5^0MudkEUqFRj}f-WQG(2uD(wmyPNteAYK0v{Gt9L-`8Kks=tC( zPJ?<1U;ovi{vRmUKf8B&U8??je|Xptlk$tEz>gjk2tWFP2v=HcLC~V0mD92gTD)qk zh95ed1!)G-{7^ueA07AuKRR)HekfS{P|(V0P&Zqy{^5J4pIlM>brsc5*ddYvJK9tp z>}UsSE(1F(2vQWZVp4RlO4bx3%4FGQ_srPW#18J$Zx0Pi*$6$7zEcP?(=0%`a2uTV|LpOwQ)q_6B*KqgD~5*MN&x|u5(3N*0sRW6(!wMR2Jx!| ziA2iI|K#PlIfYSEg{6mv4}H=1mk^(?ApROsE?%uL0f8ueZV}xeSW(SJg-}h4p_=BWnr%LZVWye}!JraVQU{R#t(q1_H5Hbw<}dDWBR5B~ zlY4I|OpfxT7VS9?g-$dYoJh_(5>l%eXtWq;G(R-f`5eL^Q5Yn>FpI`|ApaYU76y$9 zOVhX!8Uw?N6{5JFlp&_EB4$xh%u$f4GpzzCT*Fx_9JhwJK@>IwhRBlARfxg{%~aE3 zsHPi4he?UYOf{#K6AUU*CCvjvH7$&4DlA>iH~*t1+>9sH)RBjpU~j3;&r`=pE!syt z9095XgB3JxR}3^-3^bY_QS9_NMg^hKAQ*&MG)@2=jcyj90l&ihfd3W^_+`j63w#7x z7c}4=BDH?g8EM%C8wXmZ0b$JFu3=ScnxD;D%T^d>jinn~c2f3X{edze6lF5TfqNDP z_Y{`qUSZtE|28zQ82|S^%nH56p5SK&ut_ULT2p6XH4+;Pp(s;*>b2Ag@3o$U6pC8L z#Sq@0^`tHi@{qk#*r*F6w+y`|4|iGyxJI2jJezwt*El!?vOaUSnnrvLy*Qw552co9Q?qA z&4Vv_N%P<(&CP?CU(`H!#Y+dTxHv3hfzjGt|Bk|+<8B6i#Nf8Vu)(c`tp@uFI-ae^ zNd|$Np+Zebi)TFslUCTv4oLD6#}Q0>OT=GsuPW9M@%`%_y#5<)UFB#sAn0D$fZ`8+ zPBw$1apDCRQjMRi$jU6V3u~tuTDRIAUXIOeZuaebl)UQ2M?CBSF4>Dj!*>>xAm0AEuzd(t zg=oNPtyTkA^K<7Bs4ja{aG;ov2D=^HucqZc&QXFtK~6si5*gQh>dsa~U( zbo*R9eU5K`_fRuB(1+_JJPOqAE>z|u!Cd?=zd_uD<)ZtG^_EheeZ>a#xpidkQ=sr<&7H0)Z)`{9$6m@&+1QF^ILx6KQtN zGhyf&i4ntCB}RAo;4la4FSy^8|D>^w$z=dW#GhOR>PF8)9D+E-5KA*GWyIPj2csR zPugyMPLkhEBE^W^@C{M(N!K)<=XrS4<=;y81n1%NJ;90Q%-sEDCWv<(5B|9Rt-3*P zyffC@lq&Gwk2i;(+qpDH5C6VvQYE7nQ=$zAnfarWCOt3v>?yKe^H8lx0xeI&V6$HpGX+g% zMO~XWHc;p3w23KCNvhtA$YX=7=^1nj7Mc)G;nxr6SDg#3b*IqwHOZiIfW>`1x$BKS zO)cq^Mp;oCWs`B70HRtYc?p)6MDh}8RU>w%k9Ck2v*h=uCPa9HOh8dwwi>~2_Sp_< zGmq0EqxS2Ci4xTCu>hr> zg9XEx9)jRZKVZe2DUT8TAZw1?(Gcv?a_ZyJhIx%}I;9G<>2TmIKH*G^5KNJ%(h??F zJ3U4R!C=7y!#h?;42N5i{&iCMQO$a(CQ6ztiKIFaWa#aCqbP%9-!~S^-AUMVE-xWT zLG**_+nmd^s)Sn|{nCx;uz9Bs(gRg}L=bihq8BdMaUcD-g;4qjfsE6SdZ%&;rAwD| zrbvYq*o$iFBDyOTvxRAW33US4Sv3|5(@PPQIGMi)aa_=(9)%VO^ps4+hkV ztrtS;4?j^alpSN}BG3JR9k5dLV&>}N2d2(tVM zDM$_M!&%+{vOdj*nKB%pEXHqTXI?lv#atGR{Mcaua}$#4N!%a;>dD`55RyjJD5jKh zN(Ic@JX(_;JabaBIRDs7;fu|KY+97ejlepsWcwA%pG}47d)@e%QJB^j!b)DU8tmv$ zeX=Qi&Y zntmnkEj_MMQBxkHx#e(w{-QqOnC$-B?U<~D=)?vkkc#CZWBi5RITP9=ED+Hxh>i$S z9T)7j#fiPsYR9Gk5bv10q>`6B@K8qtMk{8kZI(fk z3#LO+&H4)ZYPo3YcHSO&NhL2d$xCFXLP@Nx48=Ma!neBpIO6MlQGz6EFbWn_zm8xM z=Pfa(gOqryc~@9{sbKp;!M6;pjUTk3xmJBx$aoN+ADZ88PVOuF?lotmh21*0O9#a+ z7+I*^eT6Hn-dpYX6W&%|{Rjxf@ zCovN?M@uK;#GVpqw>o6k7ct5ny4A!ZS=9O*TwqSZCu-9kuECdMw0#*6TXwsws3LhS zVTqV^3uD|zym$}Lw(jy1M=hU3CvM9nCoF3uI9{KHWxT_ZNYM7S1~`qTSqChNx9uB_b-p8+@~y_pg{?`mEf4g6VxlgYn0&FiUc-Pu4d42)kzi- zss*SrgBHb7SDhqb5nuPL%$&rWSP_%d->UbQM?t-wDXS`%MkLmqR1uS8Z6i2-gR(v? zm0;GbB$~#D`zUoZ1GPtcpM$A{CJ;<2n9XPbiwK6J6r}M1P1aA_i5T~%aJD5Ke7aNi zJ5tjWzOS$F1Atj2Mw$p+rfX6mvV4NHcfoRde+rFDwzZ^Cero$AL3W0fGORzHs|Z7d zxl@9k3u^P>1ln;)uHF(~By$*M$F;qsq_U?jtEVD4f;OgD(oa~b&{2igOso3Vgm!;~ zd+p4tWq(#c{0-H_53Z@jvScrKe%EQ-E6iT-jINVevFGcCjg#5GDD^y+&)&MXP{TD# z`>5SBW>?LgulTW>eEcV~f02q`Mf?eiv0&yMvh2_;E+k9yYiB;ON=)&L7Rv&&|HduW zuARBTr>Nri^SJ`3osKOhSJ$ZLhT58E_}H$sGZ&;w2<%50MxjFt-zf+M<|p3##BcZU zBNp%Pstx=mI>1}MRYDwYIfo|}r=(&%K3Bix3SDDZ+o%$wmlutp%!5#Wzu0;8g4$hB zn^f-NMIx^ZnrLSsOG2H}L}jQBo9#_PL*WVYInfPiJWO7I-qJPTg#5DsX3rFrw5>}eRi}m$5&M2hF4`M`(5`HvU0#*0XlQrA zDcVIWW0gCt=vZzXUD8duvW2gX#K{aYuFsWK6mL*6Px23^DP7V*F+C; zeJ;q4*G?fPXO*&5#y;O^ScekkYqk>Rr)Ln`pgi+pBc8@VH<`sQUd(}gv@*rW!A*sW z1DF12o*I3A^^9)HFT*rQDw9SGpFuImg+qhojT6&2?5W`YYcki)E>v|(`aP5rPs^Cn za7uX6y_NM^u&x%?DGt^hzZmO|{}Zx~*5$j&Vc#G3me7PX_JImxPq6OT*-?mca|v(h zy-`%S`kbA>?64lwjfU1428-ch&(JziqgMVyeu3Fzs&NmFJi6F(?EL^auO?Pcg z);?)_4nqM+_3$qGTaEZEe(3*U?rq>BIm$bK$zI#Cu`{!Pu@YW9z<{;91TBF>#^yZh zC~>bN@R%r|1cMbqh)5s^EP{XqYa=;GD=!GLfC4_E+@IVjTmS_&j&^bItZi(WI6;As zD1i&Y0X#wrh?ihNfW7bUsj8lymb$G0a{trE-s-2So~NGYS5H+vU0vNny`K7e2x9G( ze3cVKoKmo` zK@Ogrx7i~-A^W*F^aS2DYxRgFl;UMGr23;b->OUhAfox_3;MNoswQJawd7r+q&x;1 z6N!A0hiFc@?9M5d)`onkOOLpGZgR-BBLrh=5!sVU5i0Ky-=OjFv*e`pzx#PNe?RYX zH-A^H&@C(MiPIN>!ROfzbd`iAi-^1P^*g$w-1?qbc=Orz$w*tRc743@vxcMX=RU!eM@x7p#P4-P}KY-@xLLgb9&xRrZcH=$qSMi=rn6w@`z-K z&b!7XS0zi{mMp0y+c}af(d|I2wPrQ`oNh}=X^L^D;*F>B*8mN|wATS+bne?bXSWuS3Mru1l8Gl3I@@OPZv;K3Q^3(w^&+B~2I{OqK}5 zSOc9Z=Feg94X)x6P3Qc%lE)|Qd3&3Uzq z{9r|%ypJd9#n$SDw5C@7I8iHMtUlV-RPVKkdam9y!-;x%#vV%4%QW^k6P5Cmdsd=ep0VdA>Lrxp%WkQz&{t1`wXt?l+U@R4)XG!u zy@`5x>iu4#UP3*6giem4kI?beIIU*p%~vF<$4dITd=$A+@llD2+v!Y(oo8%t7>4b)(s!!}_a3t$bbAuMG9_6|%EyiFWuSUmy$K_tmk-OK}UXbQDGeQhVc?3zjZt1E-Kq7SK zu1Xd}MpIwlhnG5{v4!b*2(0w&$hGcA2{=LJ)V$k6n&_~ z;d_?)&JWSY+O@0*vTxYtpRB-yfU#juB|0{v`-W4ez8|&I^$I%2>yIqF`7x%qNH+X? zeAMHg0ZWk%%D)vAo)qpry&s-uY_6W~GS&R%)|&t3k&V&IkoeI=y?lv^9fBKJe@G&> zDzOiP@8ZJgtgG0^*kjqEu(HJGoqQ9~&IVn@oaRjxc~g(PsqV`*NMmpM1e0p$p|~Lz zr8l~PxShK&ZQ~x(wlshG^^B3`toLm=HEe*FzWw)LLs zvH_rEYt?F~>Rm#$wEe1dYi;YNF7;DM{j{vg`0xs8+)t13>p>Xlozb!1lQ7-|S6A?R zSQ=X7{(d93aKn2bRMO-?H8Smk*>`ZkujA<#CXdt#?>SL&Kk)W5&Iwg5KpQ+{-L|zf zCj3OEN_=^G!Zu98FYPr6bAGw4u(q{6`7qiAtMoXJpF+|%jGRHm=*c%i6mHN|*s#R6 zK1Y#RqKj=T>9@l7ZD05dE1ciH&|_dH+|u)y+%!NcMf`WV7bIzq+utxq=A3Bz1Y&M*FTfIKjYwBlnN=m)(xL6_qH=s<=%Ey93U-{ds~r{JG-|{KhNd+ zg7)x8I|kgZ`cJWR%6-{h&=w-o$ACXqRf%|<9MPEVCRFT#Iu$pSSczZ zewS6F(6TyGH>Kb5p3uu4M)OVSgBT;uZ76BJDNU9%-FosPn+={ZH5_i$&Obd-(sb+j zrKGf`8_cVdB~3S&k4ly_-A1k_HE6n7{H-KU(~aK^NuH(~zlmf?(~aMK$&#iUzcZ30 zO*eiQB{gWeiMu#ia?s)CZpfA);$D!u@#jk3lx(NzR_&?DlBQd=k5aO>%0Jq#Drvu} zyT1SK4!j>fS<}}i69t2D!3UcQUKaX$s=J-jb z0o%3I`et*4UPthozGv~G%=jHW?7&f_UievUt4WYaYiype-RU;ZDAXcz+DIWRFyr-I zBad3QFOi1hAdeqAP=35?!V71zA)@FlKi)Uz6TP1?evg{c>vn!*Jb*3)L;2z zrysMWjB!8osPE(02Uag&`S>V;cF1}VQ&tkZI|l@V@uJLo^Zt8 z*kRuIh%jT0*B>I!yRgy6w(J2&%h)_buM`RtkAR>J*!Zk~P1F2z@Z==w_DxZnD5gUZ z5guZ(ksW+joC5efYw^cv@Bn+_93C#)Gic7(DfAgcl-_B$MrnOc_`$}H=}0<99}+L? z)t^fhiLxgzKujEkn?VtSO(DX9DlV9s38g5mP|_{u?NSc^^h5feYfrwEJyPDV^k{k0(p$=H_UM7fxT1OTVfENduyP*1SvgPIR*+}U`z__@jisF8P^wcH zr26CXmXcjD>%~)iVdB5DZ0hU&rVo$vOdj%AseJq!KX`82F+#PrzQayZ{bwrfcc&L9 zR**bTO5`gN>T@V7;S(Q#hiuVgt6|w7UC1>O(`OyioRtzqtCR;$$|cfPF1(Yz)Kcgr z!-H7cmoIzT@KM|D?97OrR&t=ZhNIJQAH3yAIk1?hgLl-C#8}^=-YEGrIOY`)NhOgv z>giaZ-3o{c?po{W1r`$(s~39KPjejRG0ASK(|JugK{!f`9?wWXnpym4*eBD2C&P5jLi>0Hc_6S`6T+Hv(c|PrX`6&hDgvC znS=hYEKHz3>gg8H4=g4sp#N(2X)|&7_jAF4UDU>`^^!{*4vSJA6BndB2NI?HMET_@ z97Jd1Fz1-&B?=iL!9ipW4g)D123yA=u$ZWT!*8?gn~B38{2VywkaRp6uX(1!VOGjx z;*6AM=9Tgj<^B{7qO)-rb4-&Gg$$A4ATkGsN(zUlbsPeVi3&KpnC@olQX{Z!piSl63;q7!Vj!;10L~VqC zm^=|rY9D!cgE*})@nGiPwFfgpubsy~4H%n!<@gQXBQT;u1HATUxS-Itz;K(pF>vb}q{T0x?-IQU3E37^1Vms5z!3 zi9&`*zz~@O#%v0Vxz@o5EGEi>!8EEaXKH;H0^_MwL_D<`@#9Rb|AReWfww4&!DT@f z6TQh|<~!O-PAQ_Z!JBhT^Ad#&k$@*M2fQ&vs&<%KCq3Q5loD7>R2W7rPOb0d^oMYm zmGZJ&$`idw`HAv@6b_=ZaTs$DUQ2Zx~)4#TbE5Lir<$AO9U-QAT zH^s3>ILR+0Mx{PR4oiK`mP-AJ@=H=kh|Wf0&@l~36f#7DgvcBuswpG}T1O(Vn5Z9T zZuy_gr~+d^7GP&p7EBlk)t3T8bT$|jnN8FpQOFPp7$S4P=t+T5X&sEfVxq$Ecs>sX z8?$XH0z2(IoT4dwz5dA#haM@95#3UrAYRH(l%JNuL3B0_CCAhyQOFPp4kB}KD5Y@d zY8{8bVxoQ=r+B-cGdpDgb{1s;M~P$sCzDcOh|UJ1%`tUI6f#5thR7T+I#OU1TL&Ys zn5f_sm*3#DXbL&PtzTh62X``k<`w@zgw`~X{L&Mh&ul)@!L&h&i6X&cR|+Q4*)Xj- zrn*ESLnJVX%znUn3g0886v?!WDX81DI8W?$04woD31e6%kFWf$FhZ{yakXv zW>vW192{mn+hxwvG}9E+ckXdeEy_MnSde{0BeIX*ZBIcZIvc7v$22cd$PfusB6Fas zrJ!1B9jd@$qJqnO$A(S_>QmG0i<2?@Z?a1%qf)kt&bD>N**fcO6^X4Pb8MYY*}BlW zt%1cvd0QJZL<=$eW&FsP*4ar}fSqHqfPGq7z*cq&4AI$O3_GS#i9&`*zz~@O#!L!~ z+19}bEGDWkM!tG|&aC-h4S}YIK<7W+Au%NNF>+Aqvr#1V`CLy53DMa|R2|cRL?J^Y zNQlfqVl;)sSnEgx784bQz?a^XgTrHWf8R7~p7%J1LnP%fu_EP(o}~Olc_f8{=xiLi z9aE1)AwwiMh|IxZFonZV>o^1!6BTgy-*0qUXpxPk%{JZU>6E3?a6se%mfMHPahd@8 z++!VJU9u7^O0ts8Az8_%j8cG!&IYXLm^vj286p8pWDa0a3b1PHfCUy46#zRkZ?o|6 z!hy-PvE2RxN31oKbVzwjY?JbA3`zNka&HO;(b+g`@W~2FH%WwpNN^CDgF|-;ho07P z2rMSb3XcbymyviE=v%9t2LOP&v_|_?Y0X}xwC0OVDF8%g1F-CvRwN1;A^|{T z4gj4g07|U`5Lir<2Y`6vPk2)+xxS>wQ0lS6@kef%UT-cr?^u#W*j1 zlGfNZD6NS-q%~hkP5~e~8-S`~8jvVthy(zUIRNOFw35D2YaM{VVxod?yo^^4w$2;+ zsFFW=p$|{F?baH08V)Zh&xzFDGo0P4lt zHto|3R*whYDwD5`T3t_%pMd|y@|ek5)Qb%de$Bm^65B2NuE0BNGeVXgR`xQh|a-&6Te4e!K#?2#N^IR02Nj_o71+P8Q%m-~-B zL{-|_9#VfhKkk_vEv7yCY{&{7le3g#a_Sq7$*r1oJb>|5E?df?YW13;K)5Q*XPB8D;qPrZTkM)>@W3tVrEd7-1EC?}F8da)zf8=|v&W5_WLOB6Ch zVui?@-stvFh2H4#bPK%^SWJ}f4edjP{TIFoWhym;5eLv8{)}4&2UQ8{2BbHuq4Xwz zPC+0#8-mC&RV4};B7s0;4g{Sk2uiI(5Lir9n8x1ugH!zQSjKtO1CL7INgX=hxk5$C zV`7h#XStK|#L6igL}%mB<(Rr93K=3%L}U&QZ7Cc&TE`)}! zyh@{_X`}L$yeO;?pd@>+x>NSB;K?4MXeQL;C=s0vO^0JDN)$3g0*%NVXzGYm?J!C< zJl(=5iCM(zp-GOB4gg9=L-81S%ctBJX_MC2w#j!PQAZ@y7ZI5RKy)?$b$BV=AdyKT z0YGF90L#)k0lpJe9-f{KAriVDX%#EUzY%ul17t$gAUPi>{w$ywFKfIUxzQ{W-QJ z-ClHdKaD!3F^NKkNGubX(@(P=zA*0RJl%rZ2No06=qFULenJO>YI>I)d>@v2xVPYJ z!1hy3dvg4IjcUPZpr>|W;xmxXEI9CnR2f_bNoiFi)R7c;qO-xPI;H`MLWW417nuXz zSPHz!*1-!bCMupZGnUlOpH4?t&)|3R?AB3wV_ijh6BbBsj+vw&5Sq^p_h(LN1?x!FSoee?JF?C85 zGDHG_$Q%f&DF_Byhaj++C=bEf<@)MpYYV~FnCoHZItS^HNf_QHlL!iA5(iFF=!nin zXXB@>bdyBrhy)#xIq3AH(5bYJPGB)nv4gCA@g>O{5qGdLn%>za%x`YFYy2t?mo<{= zvJMPZWgWqTtm8yV3MkRpKrK6_6^TNINPrTV15_ymR9EXj1r`&vWisJw)waMlkE7;u zIcNDPbxEpYU`?uX+bPvKijqP>bT$eLj%iV%kRcKjMCPE-kwT%^ItqcsL~TK#+4|!@ z+?~El`69ST`rV@h<7jF_RPoqO&2GaZIxkg$$8EATkGnjSgR1y@l*ePq(ln z2Nn|*L(sTgbqjMd6mcki`dw~9os`zJKPIi&4UpEHFF~7ROd>iPfMLfpDpAM~2>>E< z09Z)@u-ZBRfyG350MtNT3*$k%S>Kl0zmbyhLZ?Rdq}Q5`_$r;3YB#uUZPPrPlEZEGEk1m2=W& z>-70SK(jzZnqyx@niGgfbM7osAc)Qeq1!R_NE9+e0)ogK5av@LEVK?nU@=kKjG7MI zBl8CKZ*TWuh_uGGlC&lak=C3zNdX`_8-Su?>XayChy(zUIRMP00GMqZfWTs+VgNFN z(XBJ*`%ia1&>_9Cu1$IqYe;X7nxr5QoejZ8rTDixPzl zkw7Cd2bw4aO|^As0*i@i)i*wHtMiR{X^m}j(weYBTC)$H0zh;&05gthR-%w05&%Tz z0MMNRpr>^J0*i?XzVX6eZOu1+N50YYh~U+y1PO=rWDi!4$sPg@*~9*L3L4Sb&L0jN5r0f|C} zNB|I-13+5}fR5Gy2rMQl_{P5HZ}p9x-lAP2KWW+|zw!ryhv($74FoE(jTl3=u}Mx) zmJCcqXT#L(n0h1%86ts6WDZPqMB0E;Ab{)*Pqz@6$1GxP)lXjbQ6FPSYuYbKYhny( z%_cb_lK_a$2B7GeIwcAjA^|{T4gkyE;ILb`;^`Iu2rMRQ3jnEgw#Y|2U=Eaf+`RRB$`X;}*Wqw9VABn`$ zqbUGHX9KY6nCcRR43PjJG6#UU6ae$B0}xnDRG1I==!I!vcVN@eu=dl3L4vQF3@Vry zbk|i~SKW0**A=^_T2w6T@T({Kq_@Ngzgek2UO&C?=C2Tx2nNgHV=Kjv=wmBupC&u= zLFTV(B08fj5zL&}lw6*!2vp=R3sj!22vjznX)QSpMU$QNbj2|(Nfa__62;RMARdZebh-784aaXo&UsRR>|0+Iinw+c#CS4Ws(Me&0c`D7~?6L3;C4MS8RG zoPt1fHUx8yX>E<0O&~pP-z{2z+$320NZ>+YS5`Sq@ED-MV^##YDyZ6Gmdn z4S!3MQPVm7r+r&J!z-oI8}O^rn?okjn=gT&PjWI6o!vtf#}r8vGDHG_$Q%fYDF`}S zhaj++s2GAU5+A@tfBD1CMSG+*wslKuPM1jQiSoHA07Pd4P;yLN5`_$r03b34fKB>C zwZm-O);a)z#YAPhXwj+H>D3d7dLnbwtEbf4@N^4{Ma&}BxPLNS^mpZ=Y_T?O{@?Zu z^$bCe^alJk>CMKF^d@daXwpSRXZO&?!>n|ZM3{>N0+BfoEPInf53P8*1qcF*iHafE zp^Gkj$hl};T4UR)v}R*ST5}vD1%T*m0G1uoibNqpBmjua0bnr&K&^EE0*i^tcF~$s zZ^^4C67@vps5hTdZ=rSd0*i@?`zOOi56DFsr41MT+Sk-Ge78b+1O9^aCZ3VrY~P|! z(nUpQ_t2bUnwKbKhy((WIS|aGAee0(g1};;JOn(|sItC4&{K^G0m4mx1CamyRR_qd ztiaY8SwY||E7;6U0U|mZkTJ(JDN)D}2_PbK02xgIGS)gEfyG35K-Tr`v8#6g$zegl z%QOv0K5^1PGAcW;c35_B&_;H!y_iAqB9 z%MOwU^YUosozNd`88&~l?jRYE9avkH9Rwe;gKgauB%-q+sW_%cqL3jHNJQp95~U!i zwhl>PF;QD)4rW-8iKeT3jfA?#YAmEqS^Z6r=HCCXxhhp|C)oKQ+i`vQF?Q*QF^nFn}R@e zHUw>ssY9ZWArc5g=0MPyf}qqo1cAjw#p9!K7%2t98<-#Bd!_H)XBIs)WvJ+ELpRR1 z(oGT>DiT9Q<`~+RGPI+0Lj#M6@`k3*R&KKEA*DJs0x+R2B{^HUN*c~q>N=dQv}?{* zs$Svnh(B8yq*&?pXDe4E1bde)<*|?n<=1CP8;1mW#GE}hZGH3L;dF;aWX%g*B*hq! z*z#>`OD^f6vwLaYF)c_GGDOl#B6E6a)x#H}&bp^th&ls{iAuO*h->&jvZ+J>Xlb*1 z#wR6@GO>S+Z21~QhLRTXR6Zw=k7-aRS`P}WC2l#Ea2F23JlTNU__3oDpAM~2^b=C zz?e;eG1od6fyG35FxGyXGn1K<^&69u^|S9aqmHC(6`gHskF&MnZ54^FB6DmVOW8Wv zx~+l5L)-K5o?jK|aM~G99h|Y#&(J|E|3K=4SL}U&mr4%GxtwR!6 zOq7SDaX+}zo1q$$t0s^IsgIHKQlCu~sn0Ru6cVDdk(hN%a}tFNksu*52Z@dp62;b$ z2rMRQ3lgb0bnDH~fBT$+U`Bdl-K6wp^_SipBThjeIvavf$22BU$PftxB6A?vu<*MP zZ)|$Hg?J;dn5cMsWUjAGH$z|j7qjRjttJC2(b&<8|vG#Y!8mTUW&hQ)B{e zvGhm2>3KY`YvfhD&>m7;MkGz#gB{7<5KVT*qvpG=3yC~9|(-SEOL}x?L;h2gNg$$8EATkGn zxfBHRtwRu4Oq7RU&Ao0N!u`|?)D+wf|C1S|w;Pk)EjruQO+MH_X`8oIB({pov2`+K z>rCsm1{M<)Y&~o5*5H2)iOFA6SqNH0@NjeMY7e zpGa@4o0s0iC(@hWR!Ko1Ivavn$22EV$PftxB6A?9q#%e|haj++C=WrrW{uZ)XPMn+ zFSh8{tr;oFx;04})-7F!b<3_RCP=P5`_$rSSB*3mr5SKu$Fasx`nkYu$U;{ zOUV~&_%6DsL^x+@v+KqicvpSOK@q7ER#r$cLnPEAQc#G_hN9as^+*&lM5>dOn|(4!5>zcOHb?hTs_9QL^4~>pa*#-hd*+#S@+jvb6os*+ObT&+Fj;TYU zkRcM7MCQP>l7eZqb(jK+i3)R=e&dPbBig)dW8SJicKXz*$rB6hJxe2xw;zB?7XHCh zC@f6yErqrQtKE7784a*t&9Df9mv@Ulvzn1C}n!2ErQIz+FNL4$;|g z)Ev{2L?J^YaEQ!-V?G7PLhEn@78B*+Xl!6KPp`+{rOyP@Mk6}>@;x%0b0E?i>lUOp zfsOR$90+xjqe^r(1appQUZRj85(q@*KrrJ?4wKKUr(2kO0*i@CL4Y6F)4W_i_=`_F z5N4%0_RUChLK|t$*^d+mqO(C5b4-&Gg$$8^ATkGp(G&<{t%DF)OjH;ayOGmyhKwP> zN1+=2@SRUOC`P3}Rt`&lwqT?`XFiaZ^as(|Pz*YzA&EkUNT3jz1I3^>J@~_rr(5uc zz+$3O{*ay;w;2+jzS}`CAic4!D!tj`klvi}NI@Vv8-j{siX;jdB7s0;4g^sOf@SkwQUqHVP%j)Fn~K5D5w*b5Q6`q0rMh z3W3E$c@+40>DE48cFUH#sSn@fpy`x7SY4DoL?W_>a~~;aL}x?O=9oGp3K=4SMq~~& zohfKatwR%7OjPV3AufG@_jUgM1|LT3b^vUUqWmTamOeBEfaq)hRvlAaqL3jH07T{h z(3S$AqjdlRi;0STqp_)wS~*|HBpL6xwSU|!`abuI$=OhJwxP?;&=qf}NDLL3V`$xO zNgJX0hNoMY4PzFudPCP{`GK6q!r-2zvfWhGJ#Cc;^2hr5F5g$$9vDl!MwWe?*Frb>YoPqzSTU@=kK44~XN zeCGGv0GgN9v_B`U=>=)cu5}6k(b)jZIHp;NLWW2H5SatOVhVs->i`566BPrn<1MLI z+$Pg`30it%-I(-dr$&0SYmGih9}t}l!LVZ*l_+G01Okyc5X^a#XS@&0d%6W52rMSb zLy+E*n)PCfzL_Dk(w5X9Y1op|b=Z=!Yqq3RuW)$E4!Y5uUW%1&za=#wAsW7xvR7h4 zdAvgUBX3FFRg+L2uXv$7q#!Jk?l^*0$zBqj-Ai4Lsav9uA(CDanbS*S-tZX@-=wEo z=%v77qI@qkZ%G+s=wcA2?iAuJsatM!P?V%UR(48%f*I-0A(0dmqO+msa7;ysLWW41 z7nuXaPzs9S)}aV2ChCF4?A9}7$3EsDXp`Ppw^=j@L_%r%H0ceZvmvO%OX&uQAP@-z zB6A?9rXUz-9fH7OqGAX_;6t~{(aDaxT5u zuuefBIvav{$Fv|($PftxB6A=pr6A~P9fH7OqP9T5H1z=f&>Y!3!%q@(G6Q30Wd`fJ z%wXR-g^1{EL?#{6j6@+rB#4O2L8K#vNU?Q90*i_Ah^+m}cXK{{{c}&nGuVf0c*a|g z%l;$fF)5FUqf(wlUdpqlox(wMHV#9MX;`9=Arc%!=HRe_I;uUR?nCybr(0Mz0*i?X zI9$sf@iu|;j(!Q_?=*m#4jv92lXgeSgR%`R2V@(ufox+>8=aFAs_1N(BF9vfC}fBP zCXqQXt)yUDZ5^h-VxqPTpq=bF{r+D&6e?031AC-8L55UkS2~4)=xh|a9829VSaf*YTFho33-du`I2zZAn`5L`GV(@0OI%E%9&M9a_ zXG1gKmWEbB5L0h>x`miJW)W))!4Bt#Uweg*AEY(5bx3RC2Wic= zF(Q)yh|UIJ^QWw|O`?z?5&%Tz0I=*04gjopx&;6Ni-`*J!#RAKVatb_yAfW0^RL*4 znspP}UN}-Kw_o}|FY`P{Z>}`G#((+;1Q)RdHVU@7Y$djE2=M&B2{zH$u&p?zRf$4| zNMIA01KT1ZlYUe4bPKQr78B)ROYY&N0_;@1!qEZqC(b*`>I;J^^LWW3C5SfF*q_;YZpczlMfI?s~QCm<*Il|Vrr2qZT90W7c z8|x;eH*=cwo+wWQ2>yf%vPEY@FzT4bB#O!+fk0#?1ij_qh8OgfM@8(|_h`ZBBLd*6(wl!+_DmSCy{l97$JV2I<;c?oS%rpKWlDs9;vq?s5A|3m0n9 zK-9sLbtQR#x2K587Ls>@8om_73v;T!;3nYYC9VNry_3@6ZKh(V;a` z{q4P5>&_fk_ItUnvo0NjRF}>u-MybK-A9*RO_u_ac=k;#sY@m7xU%}DPMlf>_8Qk9 z{vi@5CDQB=|7$Qp-d?zHj70cul4v}F?}&!t0gW$>G>{wAcyxZ|`>Cq&Xs&920=Gsj zqhwOj7>{F?YLP__d}VC`|DEIs zeDv&hdRh+{yUvJ<#mxpaF4Y6er25-?x6Um|J$itjHntv+TIzv0rEw3uuAAR5j?u4C z)`Y$!f<8~Nguapl`hvU3zh}u1)|CYQpgw2WTC*7)0B$oRXJ9Sc;l(`iW=L@1=XP}w z|L~&*j{v~-1Crpo2LQ|<0igNyF2W%3$@D8q!E8Sh#@43&jj8zfF1Y#*-)QXzr;{$2 ztpqQVS+@rb_trwrOYQGx!}#vQNoM4IvvQX?K`<{DT$o%2vs-mp69Bk&h_nP2Xg=(?n=O7x0wUXJeC*MV|$zKx<%?mR2~5Jmpk`#6q}| zOeEnxBP`aGmg>KDMMGH6?Tkm~53vePM6Z;$!k?EMyHmdsa<8hbbVIg%{K~`X(+yO-ECjFHi2V38fWELh-JQXy6vS5 z*gt^fr8uC{ZE8&`4jQ9~DoVDg5>2JaOn@@ynoX;q#dNgJ^aMHly$tJnR4uB)n8(N#r8zr!{Q zM$T#YuOcf|vv;<20Tl%*TK|wMACj0JmGwlI&~oAJPSDS~tnnPtJqV+l;atB}wFh*~ zj8AkI6dshq-A-X#sTa?rVkuAI?x13~s&)mcYyCqF*Rc2u@7z$84awcK8TXw|_tW>* zZucc8qVcu%=+R4lLN2!_#}Br94`(F!DafEORR>i%P(Q+{Eje_Btr&hHe){PT`QABn z(!J5tt4oEhGyeU%=P=z~UgYv$?D8~zF@F5M_Pw>K+oGv|tbb<84H~TJQy}R(f^Kno zkwK>rS;k%XP^sP%Jy8WXiS@H}9z1spCH3!$rY0MtddNrdE+SY|T4tVdD2~ko1MQ zch6E>kzPx*r+MgI&QYGI0<=cV-cLpMf5VT^g=qXldsO()3E^*ImI#qEsy0_^ldvT^ z$?!pIP36`)`DO&D^f|}fjHWA{QnD^4M}ggDbo%8*WdoDSE?z3UT{Gm~+SqrsJb!Ye zQw1xsezo9S9?w(>Vbdqg#nnIKCkLXbbMb?I`G=p+X-{qeKTgu8Io?)l`5IkNu`b}E zCtP&v;?CakQ_L{j5WD1^bW=Pt6ehGHMbi&uGB)R<>#lq5kqyd}yaE)a{adA0DuQO$9&q^#qO9US{Jo zZYFmgNRj zVdA?2gX}6wUWFglb{&%6UWIpF<#x3BM1j>9ltf$ND&&xLwxm}gejnu2Da(1R-A(bM zr`8M&`YS(jI)sf0a2|UY=Q;jpIFDV(KlRC9`mW90R5Q+gT}_JW?mm5tUDp>5)-Cf0+jBK4t~MOVQH!3pqRV1;U-AU(G}gV=mt>`g4BpG>rdR; z(W>qYxeZy7<=mbwlO_Uw-D)hT=&-wMsz34jT8Kj2&kn2#p%;zchvCpQPtTJcny_gV z^ql>YVJtD7Hr;ePna9TsO zi=e3bruOLYNcm02OK{qev3h^x`UC)ctbcK13_M3}J`|U5cRmKAM%FMXI+%kDH#^8M zJ&9YmI}6>sj#0IKDQ{luc0;y=p;wrAo&{sRVe{JXl;u)iVz$%pBy83mPU7p+$(3k& zxD<`w*`+RxgrRlc^pz;^Ts-4hkNc*g177V#95KO}km|_|DbaPtecy28s)Q46R?I_M z->^AjZ5j!>dqg3B_h_)~U_-{;J-S=v`0h~(HMV5J{d@D>qc&lh?jB2IOiq;fP2INj zHVZt%mWbA$gC`9d{67*{RgFQe6vcAIKfntJHhmBQH~Q#l0>#F=D<5zKHG7iio8^d! zq5qc+2fca_tUn4FHc23FGSY%>cPFi}rs^$&QbYN!N=J$=8h!8%br6%)m?KYWcTKms6|KZ{m4ybjX=2mY0XmJ|9G1Q6diC9? z(;?xA*3;SpWf0i59d6!XwU7G!8GiVQ26cb9LF&<3<&t~w&y%(Tt0_@);Maq;aoIy{d!FB*h6#^zs#g4n34LFKEm}jqVwf z6!xuVNcE@g86&Nm_n^H9c~O^4(Z~|fO^1ntMe8!wm0yvEIhzjkmoJ+ z;MXc6AMzunjY~Sz1y^*9Mxg%UF|8@rnNPL*tcXQaaJc;r9Y}bYp zs`MFE*K6eKPuFT&f4=?p5P3Fsx3p~|qY3iu0g5dX{g`*Sw|JKHT7kW>=z0yNEBNI< zMtkmz3RiK@?T0&^XTv_|IXztDEBBpsy}B&dUXmy(*Gyyxo0#{SVDE+;rq@A`(p}>!n2fj@ahu{hg89 zBe2mwq;b?ElPf~JJGzzd-VKvSe=j<5FN@MAnD@SHLH~&_KRP=5o0fwoNv$}Vp2EvQ zU-|iUO24h(^G@d-7gdkv>rmIt_BNt4+yAkf?dj}L!8^wE{*s&M6J9B;(Z4Z$PpSzt zoAH_%O}WuHrgKn^XVNqAY|u2ldlwCjBh6i|eVH#%yZxVoCr8cCZECXEz@A@?xR?Na z{EE$V6(RuFqUPC6X_lCO(BkW4fpU#`SW_NeZk1R!rKX?1mObC9D)YyF+;#WKJ&(F) zkX>i=Ln`bOl=$e=JsCogVd+f6L!=!N08E3*L#Y9!&C#E-;^@yFldh;){v<@&=nw5z zm9G5{{_%5{Ef*6`CeW)5Z$m}Rz+IJW{`4@XTX}jydbvH!k+zBq&4`>Qq!P7nQFgfJ zq$V1~BJ!EX)Pf9@&N5K?6924klKC)v6@8)Q>B)v3ldT zb+KW$5<5I{X05(|00ACGPgP3{9h*NTCt<}HBvwosmm64EA##USXut2^$we5nURSkB zvuIlsY}2qt#^J^qGb)3tnsM-R8!+~)mqDaxYEV!54Xc0zYs#E-+cMeoD|89djCME1 z>&3!=YUe~Bo+!I@ zp)rbweYwDxvSe^~>Fy^O!-H_q*tH}Y5NjvkRFi)?jYQr+sOH9!?ktP zit2X_p4ql`i6Znct2w`LTM6~~ukbZI*THM&TP+{+AV*7P{moxJb!zQDP7}ZNXXxA? zpGv)UA&J{`#k15gcUyzx{X)GG&tdTHsq=GgkyJ_{sSU6Q&lmw9lL#eCC?C zXKmWx;g}DVf4(u5xVTBSn8&i!zlTZEAGBjCS(3T9OwCfa3s~=a5w7QQblXWaEd|;^ zRvLCEM;;`x%DJ=cle$cfB8^M6s6SoHwe=UzNq22I&cRY=qozesySCmt@5&VQV|Gqs ze^6_-)(5(W=PX(zDwb52Vh3P;%TvHc6}WF%&M3zYm` z#-Nr|)&p0c6|+Nb>&a+pxJx&q37Xa;oAAa^(XF~2(e!zXpS2ZNm05A$4dqwd>+Q0w ze$i)7oht9LC;RpgCQj4JriQvqkb{_#bPx5 zoX%+ac_{gNDC?&Yyn0Rw;)~`t;4xzgpZ8y5O)F?xa+-E?d4{_rj&pSjMK`AvcJo{D zyW8ro`Ry|?7)`>Fu>I4O?)}qI&;IFZW&iYW^z7+MRlv)X#%iurhOQgds0ZiszVPbO=h?=jwf-8d?^f&M zcXaANRO39vPU5NR*LNIG?R(xYb4xIKhK7v0wNx(*G9{?{HR#nFE;PEuLMDr?-fRT~ zTS{^G8Ui#K&4@nCeBrD2*aO#G=W;-NHg*5F18^gLj~wTFOinK)i63MNH^Dl?(b714(Y zT@b$aJIAXuQUC83YC-mV=xuehl{CIulZ;_OdhGlIS6+YYb5 zE|JnY)miP8M;NKRXsU{P=y|_)4a!5C3sz1br4$*9q(O$|Awzk{C?SJc-OC7%*&qc9 z)ig2`fz6V#fuIqhK`zqnfk`JrHus-X#Yy+z+D6M8-lI8@iCV(g9l^m~uZ= z-UrkOP{jdN)OJ;PRCb*e_$rrnfkhs3-o4oj& z%0r@QZcz1*tFO_jxdBpjan({ryroQkN~g~o{B&5|t1Ri4L$SIm(74E5b}PD2T}2m? zQ7F1L5>rC`voB+nri~siyPI_Fmke#czG1+iq3+hVLr2VWWyhRVyUDF$vd5~iwV;tP zeI`Lthwqo7q$_qN)nDKAb){(PzP_o~b`^@J7mB|`)N>WD7hKWNH+5}y-_!+#n?EJD z5uf%zdDcJ9`GfD;b}*|o>7l+i z{p2z>JFv1F7OeE1?vUQpH;l$dOYv>bk|Dn$mxXy4JF22t+i47%Nn7T`mnpXBz_v9iU4$M1BSADwd z)0*ZZ`mzm6^w&yV1r&yWViL(=9$g}xWH;MLcC$(8HqEkh+do6?pFVs4^wkW|R~7e7 zzm5UAj{&-$0Semapa5)>YKZ){lB1@%e7X*)aViUvQ)5-LUV3VblnIh9Ww43Pni?}I zZ$CA<%16Cq-$G+*6dv{Rzq_e%6+qQ{n(s74PM`PwPli#hyPX@M{N2v0?6S=!QB9zd z)5i)gT(&7Qnp{?Qn^R;i zt4r1#Pdie+)4d%+T^=EqvLTj-P)6nL5ptE;M@zQQ@EvJauVbg|cPgirRjcvNb-%2J zm_nb{&}?O>#BSjqWyEwGW4K#nf~gc3_Ir!zfz;_$bt&f+^1#TbygeAM@=+%dEuG}p zU8G(8v)^!FIF(aiSdFQ#ICrV3hqWTuP`k6f*ElEq%*te-R#4L(pr!B(J!6YTt13;~PMWuMl3x5UBMRY{)~x@M=8i zb%8}3NQ}{WNmy35gJs1l?NZ*U&4VSQ^7gQ}%150VP>$LSa-wBR88M~T8&qJ z(G9anGo~@jUi%dSi9?p@#~N-jFGpa5OKCjl>^XvR?rghWsEw);{ymGO`qQ=U1oRhg zw%t|f5FNRlhdX)bE!VEXT7TB`wl^FFYsZBDxVJQ)J+yrb{(V$E4w%uN@+=#v@6#iJ zd2N#~Xd8A`PXlK3WCwDjJdex0k6PbXg$-4$YkzoE+$*|Tj(Cc_WTxtUu=enbXIOGf z1j49xE0a!J^heSCY=PZIhBaGLJ4@`Y>D8U~Z%X{4vKBAd^)}8%*gpTm#lEPfmQ0Xd zF{@e2>)cAk*9Z0DPUCg7k~QH}poxS$?qCshdvsS#r^ej-ADkb+#&cz3kG!0VU6&Woj2*YvI%B!c(FDxr0mQW2V6m+U(H4-|%ivv|w0p^4X^ zbF9~CP?`sEX}~U6f4Ow5AJ^Nbz_G3VeZ6n7RfDF6Bqlt_3k`AVLha4)zS>Uk+P4F| zN>_QjUfzsXxq;Vnw%0bsOCmDyDpQz`*K*b4_3mc8p54If9=*=<0C-7EOL#p?Cl>uX zfg_KQhZSzF9oluq>Wll^%7gwnm*K5WEaGA3*2nElJ-!MO=j;O<_C`VvZSRyb&+;I5 zYvyV=rD7BB5^Hw$C)+6{A`|2z6y}3m^~0i6qRE6)irlC5o%5*MYtZDB5|aUP^7#;B zc@sc4>9X%TnEi?T9!AmTVmHn4F#5g8H#P4cIbR&(n-_aSh`4x~em_U*?&md4-cn9J ze>!+l%Ymj*c5>|1%znKyK2k1eT<`#{*S&gH)X;;p0>95m_%Ww$Ad;D})NLcPaA@z? z789)j`fEIOI^rJ!xt$+vnGJ}Sm1T;Oz@c(t0dSLueiW+~R}RqqbXl{I8zrzh++l=fAN7J4gIeG*l0u z707E-YvgrEE`MyZSr#|P$`baQWKTA>P&OcCpR(D}pQo11@L{iWm3%P$Jc-$Fc2QrN zhkI^4)*XZvpf&0#T~K|%R9f%1n>TwyU zCoPa|Bl`uLDhAlxr-B4?@P-lIgJz?T3@PY@Hv7-oNBxU;hF!SF=trgSXi^2JFef3X zFi#4WMj}(>4zXS=($ghrL%EyLsT1}E6n}iP zuFO>x6FdiLYN&nvf02CNa9fsXve8|@v8>MqY{sL(-K$6s1T7!h)xrAo)+aq-0YNJBum|xkT>#EjckltUjJ!n(!Ty+Q)vA`-x0-Ylo#m5 zvF564*Ah4@3jn!S&=+zp(wJD*nBX44-osa1Ps~!&M82f&=lD{tHv8KDv2V|(RB<5= z`4_~zsL$(Mq;<7Z9aK~mR@jG@dneE4<4Mr=fq@dQ((4NYJ&p4|qpVZ*KBV{}NvI0y z0m&bfm?0GmOX~q?U6sN^YLn6<+)zD94hGkE!X&S1NRt_DtX0<-4 z)v%vok9br*>C4f;mG1+ziz}0_0x*E$0X67wM4M%a>h^~JWly@SF!5e~`pq2WGMwij z{_2{-#EWPq%C-KGqihLRmgEePqQjuib}7+?756C&V?3|jV9^k>1txPk znZm-&E+x($d_li*Uqmj8ladWyA~i+cq`v4b+DkAI>4{(q*;0pstW6LdAT^mOU@ z=;ggjC=!t)m8+ycER`(_m6f$~l|MyoR#>T1J`44(XwbF#F76PfP8^yzJ3{wEH=ey; zCfr#5Me0!X7f6J;FqQ6N)fY(;=ysD8(Cy*ctEz0FtE`=?dXaGn-HJ}JJOvWXl|0ia z!@V+aUO=WaBvWNB^R#t!b|-XDr^K~aN7+(GSvyxpen&^j>eHB#ts{RY^!NDcg(p_e z6e`kT{0o{?3%6pwGLrUh zz!vP^Y}v1@o$D|Qx3^y>aLxWVH0&>&Smg#Yw*SNz&OCcbv~&u1tg1oq&?8o| z<;9kTF3Q@uF6W|)$K!PH_&m(0W=RqcWhC)ff)02rbM5g^w(wBa&UH{=*D(LA6RQty zGXLBr^J6%qQh;Mo?Ey!Pl$k_uSQe@%Ysac+{nVVqq|zm(Nn<9&Bt@2y$QDWIs4pE; zDU+Z`YKau{Fe$D(WZ5Bg*(#nF@|4e!!lguWg0d;q3;HUZv24E=N;NmfW1jNkGuUIk z_N3KjVzCs3EkCRXew9N?&DUE}>LZ}Wo!_W09kvuZm7G_?n}Z`$~$* zFIsi~x;bpBD>b@EiDT?@9z+q6roJ)h^v$wptW+9ri~r|GEGQ<8P(XG!pntN5zAzzs z-0%VoAAiAR`k%VZV({=HtJ@6_W^E>nppVpJwjsN~Y?V~E>q_f45FSz8^5`PFzoQY= zl`fm4U}mcQzk}JjCJmv+&9sJl*q6JIY|&&(g_mT!#< zmM;y>;Pn6boxk=!I~>jnG&|Pwl>d7z3ko%=QM{;>kuYe}FEfaxM0K8ylv2-9qFy%J zi2{A$u;Ndchb(0pQpzw=+7$Ix$p{PI0N47H9Mwm-vLq)WrE4la+og1s6>ArSsJFNl zqTWu6dM~3;%b?Om)caqdSdo=9N4=d|B$XYa-X5+CH!JGZRddwaMUJM+Zc=cP%9e%7 z%G$ZgKI&Dw7WH1l1%4+wGwOXjg~S$*BN67pRJw~b74;U$3g~un?NwE_&{fvXRlSJ7 zNGo}#i+T$%qp1=`h?H>wjiO#@NT$k?QO+h3f;w%Kd3BU6b(FPpb!KrjI#O0QN4@Ir zM${`ELReE*L-g7PDctHc%c5;%?Y!*}^#X;W-uqxi6-$y)o-&g5FGC0WSGe}}D_iy} zYv(%5!tL#Ej(VN_A?o$^$6=my3V77iAb2d1@}KaqEOb%U&UFbOEDTLRvR6~ zz4kXIA}eg26)RQeyeOlEzvVZJ(t0A%@yh%PA?`Pl=*DHrRQ#W`sia z&_|<^fCjwqfNbrW7PYxcu+y)oLsx9EGRz@+r}Q0Iu4RwyK1z3XACro1MpL_x!}j)A z&oyy0e}onm+S$1{V{M8{HN6>4Q!(C7ugkyxyn8URJ#@fUcd!Gl<3SD@L?3P(wuc!ffZIzTYRc%`qZ7XZ%ZHE{TC=>(! z6U_LOCCQja8A{G(kivQ|`U8ERSiE9BU$zk2XMxh&Yi#Lq*)1FW!tJX;gDhBhGfy9cnF z(KPFc-{S0Z2ML!Xw5G!GDRmehyZSZcwFlLxZEP^n5$D&OLt53rwwEOp6-1$xKb$ocD1nQ&%K!Uc`>{{ zXl%SMX{pLKUqLr*=vigFPL;h074cz0l~jLxR<7?o z{t)!A-lqClD1Qk0A$DmydSd&STtRPBNo@VhsWV}=uVD#p^EDg+%sIA11wDR^luog{ zlIj6(w^GV2oqByoH1*V&WUJ6sw-53N@QSAT!8_y!#763LJ&X1>mf#as zMe(y8>C&(Z#k1A=8bFDyTQQ~;^U%0c)zMn)nl`$W;wT-K;wYB#UVce|s?Zyq*K@dBlElk1HT#Cyo ze`n)pj;aE2#;0SX+m9n*rczYsp#*80BVvZv2l<3UMsHM2>sX0#r!Z^csP2$RppSbRGr zOQ~-u{7NOqQkU6lN4mHHo6 zEIyC;<3+?DFD3r?U5h`QnD>>Pn48K@49~cY9T!cA5wfGu@&$=ayhE%HV>`Esrq7C| zuj+`VpI?loU)vc?Utf~T$_$P9P2&e)FQ;xPen+}?9NPqsgbQ{x=HVICC1zKBS|N?5 z&#%8dIc0qU%1>DzvdehNQgsbKQ=2KP@=iBpIWg=Lm^eN!Vd+u-=?>OU(K}S%VhVFC zVhU2G%M%KlT$`V$-*coLSwuz1#7Ijf%9lcc=hKwnKqQP$C1FV@OuW@#ahnB#r*1Tn z0ks6XFce%Hmg2>h9&woCtt+DGJN1D^mzC3)>dQFS2VNfaT>Yy{eN+288|MeLcIroM zHAZizHLvlXd*y~r7(sxiM?JkLnh9`~@FBcvdD=hSe~MA(Si`|?f6wTv2H@)NtbrOR zieKo}kVorXh=;nzOP_JlBgp#u{*kw(fXUZXm5h(|EW@Rb_0%jy5lgvow3MlxltykO zp;MGJI^~Vl(_!=Bi0=&MWZ6xpxXxhChE@b-1Ax?l>u8(P4JTLMc07&t^5`sAKcX`# z++vLu`8FIY7)jLShsLnicF?C)pH@gUYmAZVPd`j6i}cxLv#`rs+~p9^1i368Ocsxj zak9CiFq+uKQeqm_!%FLQy3%?Z>%`~2q|OZEs@WG;#HKH5KgdMjhwb%`A8l_# zvfL{=%=g2Tx9^=e0qWU2?p=@#f?E*RR=DB+ovtX!zCGN5__WQZ8{Ds`E4rnybZ((5 z#G|fovRT`Vc27KQ!I122xDSI0n#20V?_BmB&1Q;75z{D^D zOzieJXx*Db+xine-0oC{(3n;x&Tp;?DOZh`Evy|=%UC-@sy~YZE$=pkvGxDxcTieA z_$O7ykRP>_g+r+XxyPfI>6lB&@vss0MZT}ymBaf$to{-si( zc+C>G+r3K_%`2*-PIR)^5ipufZ%Y|Cq~=|gaxbnFNIEPf8nS$rGGDIzc&T{L-9)$a z)uxYAH%Qq`R>G#@n^8@oL#YMbdbAUNiO;R8^n+^psaMgUTW9~TPVG`ObZTAZXwZ;` zPSthjRJ*2ARj+XPCDxgQ(5jK{M51{cma0ilF8FlLr!(TArDan66^iH5ll|(+Yt)l3 zN%W-Z6!yH&&UC6roh$~5<#Us7DNf|mL7!HAS|L?iW2DxWrbLx#b+VMz$x>D)QrWVs z6fY8w+8nl&WyMlfCrep&NVl63#TTXmtxl@0?}1tB#V{qB*zKo+`b8gXZ(Do4Zlh8I zQ;yLWA1)4iICv+&Jokb( z`(S^x#Qwyypgf+Bu}hm@HCF4de!}S}&&Gljq~%r8wTCVE7A>Mmr28@ z)^!-wcFm|(y~e1v+^wUUTRFG=rgTmmq%%GplT=!&k?JpRRI83g^@63`4qEDtJQwy5 z-x6yqXDKs=>S9YxDZL~fjp{*5xfiqaM%!?c zJ>9COQAZcs_MoLKyUMp;@2M!}R5gw2iltr*quRt~jOwf3zw=Rj1_xYe&X4N*CSotV z$1WSA`bmGabySb~k64tXvkzpOfA9REJfj(L#{-Iedg{UkH+R$aS{Kh&R9HIxw*SH# zy{z7C_16+|MdSMEbG@$@HF%r(dQ?!+9ceW6iTb_w+<%Jm0v9&wB;FDJxGQ|z`lIz6 zNNCcdz2&YGUwtmHbKy47$N8ke`eT)Wc6~a<5DrqLD!D5BvN^R-!#^o1DVyDazH&)< z-FkY2n~TNV^1H96}=P~J$vf!_j9@X}rd7JhvfhK?8e2)XEU+Sqd;RWwrDQa$(Jc!gd z#4}QsKWo-7#~uofJtVY-8I_W1CvO^Z=y3c%Pd5kSeN(pT4GOwl?NPecZXNy0?&}gy zRxU+i#Qm`(l9Z})B9&E@H>kXU_4ldq4s|meBe>kx6l3VChp~+K@hhO?2olqy;z~n| zYs|Y=x76*9zm)Q%W_5W{51*XpsCw{8>N|h#UjKMA>m}ww(bPB1H)Dkk_wD6<@MSe$ zl1g1_z9f~p5-K^&{28MZ^!hS^n6^UiAw~4CL{lMMOXs^3^2)OT(V_(PAD)1tyB&8e zFJtOwbQ%@7gRYN3ct}f*Jysz^S+!^<^~8u#wPYcS%l84*0$H}o`gF#pV?MRuk=1HY zbn&c0@hYO{=a&k_YuR`GEmo*k{s_-~kNW4nE=E;QHGp_oP*sYRE)Dr?X!`Psm2+^) zQZ_b~9(Ho-hwKY2{y8n?_E;qs5KE|4jvr83yhfAVR%>F*Y11Z^*e??ujsg@W7Q{ua z+6bRJDohM%9i%0xIX2q<%=jAxS6RVPAIcwb_UPV}in_-hBK18|+^G}t*(J1kE}9ho zvS`$`{DXJY(f)Q1S4~Bzxk$=?cD=xbO@rjmE7RxCh3msEtdz&ikSG3GQpD1X3+>db z^3-g_&^qe9(2i)z3Tqk>OIGb@(*t6O8H6qF+&wNLR8wQr*B^-9_l6{*cSHGmfDhWG zAv#R;7|}c5B0evRHb&Jpx~y0lkP2xtV&bc!Q{iTQQRb+rSO2~H?H77pDRzp3(+@%$ zz)bL(9;%{AD?aV^Y00N~<)PXC!`<6}*L_q6-m+s8g*ZrI0|?3zK?NuUiqfVaN+8!% zDK}k(h+>K;AJMiUC@n22uTn%e+bFdtk{x&5t_$AXY|$=kakq3a#SKnML5@SB&~Bsf z6?I{YLMgdIDHuwLpltm9erIOxhioZwcb|8k=e>`uGoNS9Idf*_%*TI5ktF;qgpg0^ zo8R~(;nL1&vN^t*<85Z9AI|=iFXN$xq(}RqKj6oLPDO8im$t;`PM7)JJLn2X8w$wT z1z>E91Y}>w>LJ~UfO(IC@|X0ZJpu7A>ZeKP{!B#d^wpDF1_LX|qTpQLfGy1mE`$ROzw9Tl(k6xsjpKmzt1^Q+~MP zK=YkPS6%-DD~u(Hh5Brbq|0x~1w{e7y*Pql0OC^q!&@f5+lJx#it8~BTMH@5)p6?$ z?OSRWKu%m_Wn!$|SH!h0ak0`>dj@;1ntWhO(E3Ab(VR-_mDfLQ`S+B`>B#-FXr!%_ zOhUjKey<$Ax+(kC(=#==F;iP>mwmgz)F4a^Hki5+rmieZZI27rTM9&LLZa{x+OxOP zEfH!>$9t`rkj7Jr_7rLN&v#7y25DBfm!Rb2Nga$duMls9HDgP6=kXzGQolx`vF^GS z{)x3*SD)3|n|PKC6Y7bL#Enu`4zB!KQkI}R?l{jEp**D&(yHSh4IRqb|HD>Y(f6#7${b_^D0&5TJA&(wM#zR1C z57XIQ9t_9;3dHu%+Mq%NZR1Q7($iehFh_{_En9q}KweyIzrJ}f;%npe;qcyY#$tq7 zYX9aDH$@q}15dgXYTaQFi#llootM@((~MT3)w*NtC6etTI$Ibqp{vi=ncm#cGpTt# zt$DuRIp;yeJ!`}vE&BZ$=F?TCJ_ns*5HZe|0yEAz^SrN?2h{~G*tr+Z^XCwg=9x7Q z7ob+EuS} z-LtKoNns;|Zvxs|-&bnPNBb?SQkgzM7k(AJ-Bo6+DDO$+PvDK#h3-^hQrnKH$0+a; zV?RetM=D=TPWO9_|HPhM;wuvy(&se11JHj#dpX7X)J78fqhqPp%adDI5~$RSBUSfU z`xSdTc~>jl-R7LiSu!M2!b=w3)@*kDF}`eey(gULJ9Der&z`%L#Gw^VY^fq z?o{SYhI0Mgf+^?~mVo{cfOZ{ncZi>OgaHa&CZqd9Z<;WYfj+dGMTHpl1uxvEk(W&FV$zdvB3 zH<{K?{A7e5N%$Z#!nfSDfr8yT)-0C~n}6>@h&yp5Hv8fUZrwUDbr|R>sZF)eReio@O()N&rfXd{ExpZ`)#$4{HkQ-b3%|A zT8dT$#6N3+;1{yQeOK-A%7lneQ|J`d>HN{!kN&Y^VXREX;_L9<$G`K_9S81M^W@}o z92KsSm3}`X18LIvj|HsFe|Ytz{RIMNhbpOt(#J)oswvrO?yT?<7_2>aRaspJd&rwdTaOw5gvvj06cgDB_QI z{`d3}-R-?*yo)$S^c$z0c}#Q9C~4x`%XbnRGBXo0n+4kM2srB&q1R-=@au^D_Y2X1%1O0q7r7hpguLZXmi7?tDQ8@?18Ylk2 zy;m_b@eWy<-z-TB3UC7GkEzFbzmSKjN-v$)H?E7Jstf05lar=8fwk3sTP*TZaZBXe zsphrSK1`2pE11Nic$Wf}Rik3;6yt{*lp_8}p8p@nUxr{2g01%L2i++tqg1hoGDIMw zmM|{{JanZ+veAde*zblOV?TpqHd(-SI5Lz;AO@g2%tQUngK z&xde3WG&pb1;(1TEbMuYFMr=N2#8T?{OO(xSCWHnt1qZ!d)Q1Ij-QGo*b`Vg>uqK- zXQDjnEbn|(vz7#dm_2zk@}}13xA}(&L2Ssz7{l$KA%WVE8Y2&SPKO zHodFo`6lSo_UPZE4?_Z@fuM+Mypq2A&S6HJ0T>B-*X+5*24LRw%_K6-sK}suPSw(X zk6-8b20KB&*4@kl)tU1zzGvsmJs)R1pLX@B!I^umoSElMJO)Nc2}b;b3wy&qxGJKp z)X@gUO7*KQ-O6^7|IYktlD;D2B#cBFZy5vGJ=XU z9II2aa)(as4_JoXtp2b+L3kT*TVz26oXszy<1#&W9DDQ9;PKJd7GQ(R{2w#1;HaRL?>u z+E?18GmYh>1?#w(6yK0#h-Qd@iXr`m@JAby+rc5H|)laN2r3A;`c(V)-&{Qo`JWD2YN1) zv~Ef3)yivE_MMFW7uIVW#MvqvBmJArKTPX-jg$P>RQLIZpo;ZXm98WVaM9lKm#Sm6 z--)*xiV`a`@@!+rdi)$b+aPm-yYvGzorhrmGwJ7!o(9>HgN`<+CK4PWDf9=%qyS|N z1uhm}wz(Lpl8UAoTisHG_*NA~K%??CA~OEy&E}_zRkJW!`?J_wv}+|Hx`AFm2tL$? zkWd>xT1F5ENQn>=Ro-U^e-ubon~n=BoR@j17Bx$+WZGU>y+l^Yga(j@FPSvOBO?r= z6nV&??Jbd&vF@{g`ZT(!_r>`!qWryNl0WtPqaVbh1XUjJmxbXYfw1G&1<%yfj!}Ui z7Xz&~5S4Y99i@*?wO{`%!9)^b6_PcE8``p7>=3;~&H@~vQwuM1kmzypv|5*yrz!77#WW|I=h%C<&P{>Pazj>AXMh{&&tfUQlQopt5K##YKc)POw zlCK<;=Zn=HUo-HGqPHvmCUE*d&pQP-_c(~D3KNz&QQcQUvMP;94_f4g{~9U#9qL@| zmmE8(@)3~?n{XIJ_!3iD{3=FPxY8M|C&q1bRt+h(|I?+|Ufm0>EX2lnCG>R_o}G_( z+d%0mV$K=uJhan0x8a)BXe=CMr4QYuHmVLH>b#14B4AQvnO+Qkwvn1_3Y>Qmz-u$*VIBv-q-jYIC%p-tE@jJ(NjoC`SbCP18d8{&hYDx+cB-IY#Kv|Uc! z+*ZL+d*f$PJBg06+tF^CV3l`Lh*6VZ9{ngP7^5f|t35y7F^O4j7-^_Ms0obD6Av1h zQi3Ch5iHOdfki0dp#Q z60k>40tD+<06pG8{sGbf{wAX7ScG&7?uq%s`4`#Aw)E68LC)Qz(HHe0q27f{u6n zbh}6x83Wn2PoAhhHho&Td~OE^cA3q^hFl2er3lAfa<-5RR5$<_n6gm6P02FTPw+Z2 zn@*Ybi%ObAg(+v~`Z5~}${RA9D1PDQWLB~Ssw}hRv(8gLk`AAuAB(5R;RT#_P!5w4 z4~r8L&!8dkQ$25im}WWXUtbQRDbtv!z@ZmH^ll)B;lj7Mz&%p@!p+H{WC>J74*$sA z(d)|L3#Z88hdCgj97Zx3!x9fQ5Qg%_e+`)t;SSPNaZUG&QFRD5Kn z9P}cGK10`U7rljVk|~G2;@`X+N|r!X~l6csr1A_u)2$f2|FZ7y(k6~AzE`cSe2sv?KKq32vz4qrJ%4p01M zl0#c2W0hVM9`r(#7l$?LgZ}m9P$Ms~CjiPpFLKbkfgH3YO?@~M(01l>C|Lqkk;50x z-arlyo+5|Gok((6l6aU{lz3K7C4Q>s+Ys3-2mR~IVLoMA5EVG|A_u)2$YE)X9BReC zd3`8Z0#%X2XE@cju0DM26gfQR8%Yjx5)X?G{>{swWC>J74u8Hq7&!`>-K*X8^@lsQZ6Ka+og$tG|1ODVR-WMe zpgdt#$Wxj_HH*hN)%xO@PMP+L3LJV7kKPT$bGV2ji|1(ZZ(cn0EN0E(DH;MDO6XdQ z(%*y-5@2oW(mERx5;FlbQu8CiFi#Du{`^99?|C0wa!$4Vj{q^JViS6%($3mup3~me zN!FH$)o&mt`{Ge?Ag>W2lobCV%X;VB`14#$PHQ-ROVKg)bVzLp+uAYp_k=A!2!_Ja zDMr#aPN~)Ny(kv~hBE;}Knl_eIq#?V=33RieyvtgrmCpGp_f|KyFsn)mqSvQPVivy zZ@yMbmOxdM-rdu6Clh@ZQYI5`{8~&VZYC^v{QwRK(!V~heTmop%&T5_)w?dQu|U;e zbQ1i*_~sc+S;SMg{4PiuwT+Dz^xT~EXU!C9kOi6r+D`4C!?1PUYhqaJmUxU9iD&sV zgy7j;CYs~xOQ6%_4BjFkw_YTmcLNEGG^#q*S`f!(*^f0m)@BOtoYFiI+@}d+eRkS9 zZ>-mHS7y3hdZD{bda+ofccAC>%}l?3eWp)9EwQT-O>Vs~t#<>atIbRgg~$9BnO9NQ zoju0MwE>pva)tZ&AJcK?)y|mjZxX;MjlE^Y{0_D<;geUi$(Ad3V0}K92iP?ib&LLzE06QviT^lm^@cQa8eg`Sbdk=`wV zEd0iP&P@+_ZLoIel}_3yok{^Hwo`ID4`)7G$7?;w*M|?OEf`h*gW^yL9}5Cf`dt-ub7bT?y)B zOl6cR7mK9o_Sb{mwrEMUhrSX$?t*xmmLl{c7%8y!O-kwEg-LHVBQ`EPBUmqq^b*+) zh*m8mHts|s^<2EtP#5kkP*F*q;~^04`sFrn}@Ui4A?AL0##-c0|Sll-l-k(#Xg4a^4(|0 zl(Wzx3Y1pOsP5hVtj}ZqK(lFQ{_->5yZD~nU+?aC*{60t(80mxwIXAqtF9NH{c`BV z`@z#JF#YQbY|&y+Qh)-}OW}Gq5ZL}Kpdzq?*?$HCD_H_nLtu~)7j^BfZBrq2r^^wu zVi8|7dR`hClL1n$mqdCupnP%-<Xbu)6-Xy!Z_{z>Jv>#JPLE&zq#U<< zwedE<+elREer=t$w)RJsqE>GpLvyX_U%yt5CfUqo+2}CWC>JRX3M|*gkosH zf(_)*vgr|Wc+nS<9J(bQ7P};#sjkGcn72j_`q!63d&<-)DsbpU4th6`!~7aKI9oCI z8OWhz2~$UFQaJXxa1#IXwCP zB!^jvhsA>u&uX^BPxaieMh^Pdm&0_*v|m)<(2E@OZXk!zHF6j`b2*eOfvU*iUv_LD zhn7u`ki+?(PjZ-)cvzf}_^G&U7vD8&^ z#VZyUZo0`|v=Cx>2pXFQO#SPNrBeZ5ivna+FJjTVfmjyTh-E4J&p<5oEM`4LAJ)vR zT9zxnTVk_4v#A$0^=`oCoU}F7e%TB^12#*RK$Y1{OPH(FYgxk7 zeflL#)-HeH7)|3qsDvvaraiMI%=7;;1m+q1W`XHnUtl$h!LSA>0=*QjcLRaN)^L-+ z4pW4=&p==$OQ3293?wqnMsqA->W(GM<+C_g!mfmy2W}7SphnBcK|Au)r#}yZ>(-ob zztJ`1MIgB#kWcOmV_(3qe2#n$C#ent{E0!g7-ogU9Rkwlkdw71!=TczTw61odrgNq z%`_hgxK|}02fcx{cmGMkbP!L_0@nSw%jd(o%jd(%y}Gqy-!lj;Oj@Qf*Y2Gfki%;! z>bK0b;cmwxe;z9Feu!^Y3H{_>$F`mTEwPh<9kUDw<9atxiTxp;Mh|*0`_DinN|r!% zMqR~|5}P*{He0vR*I{!su{oC6)C-$>*Jab~Z|b3p5|d}nW6C0)hR&l4Ba~V?3EhoO z;>jppLkG5B{%7a5b$s+duKJrJIkS)rrFQQeKx_Ff-EKPW+ocYh&P);lB$3_?Xl6IGL38IB(oFgRKEKQ~^Fd7G zdy$(SnxJ5h1}ssn>0rV)tZ70{1WcC-c|O5W)vv+IyknZ2j-gH>80;tUaz5e0tPwd> zWK5q7^|->4I2qDc1Ey!>T$B>GcRh+bUNy6qfQAatGGh}18r;O7kY`6Qw;KS{IR)>T zo8|#&Bhc7(pg)}3veU7*-)M>#ZQhMDWo_!t7e9KMab&iZm(J5MJd2t;DVd zYip1A)C?tdDaQuHUrf&uzd#XI=aSH}n>TbKFc;j|&?#906)(*rcRmvlNz@qWL%OiO zNa6mh&G}5wf{=DBEcZxr=@efIh{04Hx60O?rpq`?%EZW^Npt@srs7_+eP_WDclaXp?OzrDVF z`Ca14S$)Oa$npYvKkoDbyROz3*n0_4#Cy>mUS@ z1-!j3Rq3s5A7qzQ0*xyVa7))JI#uXuBQDcruyh%8rgVLRywtHms6Wg8D@7F=&$3HN zd3U>JaZqyt$Umzxf}p^lGo|1q=E)kB-e>oy1vCX{evdqkVEj&_nifb`#IcAwi^E{W zVbGZpa~K?19Ql>@?aMd8V?!e4q4q41`pe`JS0prN$w+7(h~Sh^1VNNRXG+u$KvY9Q zzSy2?EkLt`3}{xEL->n?4&%-eGFS;2bf&~`zjKo`%De4#Y2_E=sNWh zonQ(!`l!`Gyt<+gbL|*RY>XwuIv&}d3(H&Y9aG0qhdjDiFZnD{$J8GZu5H;F$XW;Q z`oW=g2&#SZV=;Vx5JsDaZ~c_I&hR}eZRtp`9O;GfLx!dUtfw2k4=PJim*IOh`}V2WMX_eGp?8N z^sdWzp8TfDjStYzz%cXS2kh+8(=hvzKWZ@BaqBsa^Ns7V`t93-)oYqry=Hw@#}ca( znN_{8s&@lchZ+?;;AIwP#472XFXwt)?xQHp_)Z{0bvF97$+799^{+9$r|#K_rOS;u zJn-6ndB?4D12a5aGS6KOmvfiNC)q<$IYbv9y#&NO5rRpG6+fzXACm_AfGx*DWP{Bj z^ZWI6TK^Dvi5(7%SdL<(6Et+a)}QCnMLQ~IC^FKEp~+z9^mY`VjdN;XL4WL(Zmxc%~!=~s6msW+3PmqdCuAStHKHM6j$77GDO_D@RT(I#F^ zSJebb`97e2$n!>p369NAiHlik^nJ`)_v7~>n+aHtI0#JrV!79b2jU`6nr$QDujs`P zSOogJi&b>Mv#C;{a)=zXtC;OkYR&!$c zKMH!1PNIa>eHlVi*w7?xCmhj1M0s$JN--@>GEH2LL@DXAoXSVifq~T^FPX;3L2q8q z9DgiTJdS5<^ahNN5GHu90!WU)ZQZWiGW9XJ`S8d(DP@4 zo8xXJafN*anZkxO^T;;zFje>#aru#62q{?IB>P#s~wyVOFDfT_CZXRE_WWaH__G z#(-r%5W@6Q1&&Zrjo08A$<;VQHI9&Uf;7lSaGGiyF4Z{f*B~^KDt}@evgVp9@pSU) z1*g<>1L5^T0Qr+&d8PRLIVR~wQ@S@tI%F`6!ykEHqccP9gh~P8t`MQTs=y+z2m`-i z%1gY;YYdp=b;aQ%uYO~|(g%bvy@+U6k=HIfLs?#4N;X8&F47<`!D-}GE%K^1$*U^U zr^svjU%WpKEx4K3WLlltbvSa^b<6>sLg|G&y*Ba`eIUJBM6z}$BiY#i1+z*re+chk zokR(hVL(_~(oa&mT$98apkb5}-7qo~t1|2aCK+!3P?F)IDZ%1EC!YYs=GuAa+yI_R zmSF`MR!C|ufeZzwkzs$4VSkeh`%f>!>G!dD!&-?7$gX^^1cG!pDB66|%7Il!G^XJrK>tBSZX4uST` z-dIPQ*DKraCMs_5((1dHtr7d`L#7AYtPr9D;y;-E0oRlo9mob_ zWINngO^Og9GtC5b@+#02eIVUgOR{Q8_HuJkP5nTBRMQJ}fM15r#EYU zfmD$ke0h4X=K)eIRY6pP$u^QMk@NwQ ze%YjdAJ(`U%^X)@hMrUliFKf8JskKaVOCmGsH(f>+EgO)p_1*7wZ9!!bN(BiSc_kT*erzoCkXq`681g`JM^f8HQh%%=JlJC=9pGNCbc4_HONQ8MD)+F_wNq7f#8cz z<3B>-)wBZ0xC;?VoPl)E8-CN(YYYj_)zGy{b@!cPXu?3xG`j)=JumFIl|N-aV4&v( zlQ;Cdz@30V{i;aVaqD2u3(^Jv_bIyizl)xA{>Loy=?fRnLsGXAuwSt3Zp-dnuWUs0 z0@m}n@m1G4EBS*xPb0-YxA)}Ue7rL|_^!-GLw5+QS_MDv-MN2nf(}dt9 z5!Tj2m@25U6@tG6@-IymYC8^p0$RV+OzV81bs^I_FKX_`JH0HV)phTnbtu!CR=g9f zc|muu=Szj~qt@=64G+SPihXW9?3wVhApE10^wBkhf3%tK*+TfCO!%y*X)UKGJd>K1 zqYJ6A7M)0~)BVvx_kQUVr|cBfYTaOC7|oOoztoS>+(xB~Kk0E@ zNscBBxolRNE}mq|kH`=vMIEGDdQh3@bIdo(;;a z7rD&~mA~6o?YN4I{n#x9y~DDFdv}Xr;VxmkQem3%!1GIGDp)WT$C5EiG^l#!E}bl^ z8CzGv05}!t${)zP86o1G_f$Ks2pUp(Tt!HPw1jX-cvye^!J4YQ>X2P^)mt%#YUBB0 z+=>zXNPxamd;I2*k&Mh{gn=02E&MQK0fpEfLyo;g$NRECdlhKk1_FgTE23D6NWM!d zx*?#;yxkk*MP*k>r!Dh+jIC0+XYtZAIagyOrz8En)cBuZ(wCfcAkQI@FaqPdfRxNY z&wlV4xMR~{8^%%PaaeK~bqDi%(&|kO(oxgg*)4{Tq>Db~y_D(cn0g3dhygT%J&RWL zK+pYj7x$O$V*F`FFQ!w8I`GwsueS0*`-1x$qt@ri`}uE{X7u!p@-F`p#~$hOKTn5$ z>ztI%r{rhp3M%t+f*YfGRz|b#0CqP#@qkU8$$@rN|ez{#U{bvUVSsWnbivx73O9Ojf-}4pz*Y>`p=OJvlduM<% z_y_p&l>w+<8lZ?TI%8jigU`as!nEnGd+N+Pit(=N+X??oUBGvto7j;bntb3wKGB%@ z%iGn5nQwmP%iJmPh<8eyJ2%97w-A&FOja}B36eooC6N- zeR+4>s8->*^Yrq0dd8=^FTE93J7>~oQKov9NUccw_1N#~spt`(9>%t*XhC|0^k_Kf z8QwD$9XA4dGZTudsg*Y~_)58>s?3lgybOVkoy6J%MWUA_XuZ=cyc*gxKF;cg&j`Qi zPNg5F%9za$NqBQR!h}!;Uzb>hUHTXroH=KZhEtt+M`v~BSGuY*z7~_jS{Et){b0v` z5D>KJhZg;tq^0$JP3*s6DD-~$^3bePsWQgS2eB*YIYU1mk{0z*vC z%~ll~ zKb!;ES%OWnCRl?p2ltoMkYO-n7z7!;00q@8BsReko-K3YrzzG76OwGdUb5GK#HRXl z&o50oRH_M5Pa3_+nuB_=`NJv3_`HO%dNjh=2}|Fb5P+7AZ4&RA_DM^ele#&Z!D==V zY8nZPngvT1rDJj7Q&l+B3?=oVdc9OPRBO5xj1AL|KU^oz14|Ve*h7p4&ife9yKF-I z2`^(sl!j$OVI#G-!AwW*ws@R}?#XTed^A0#58@j(S)Zg-f~I}EUsT)oyJyXj8JKJNROM}vS%c|l~CV}9dVBE3ipQMbMjmPbQ* zax|PYKN>X)^cUjy&yd0$+aRdVRNXCA&|SNoovrF#$Vq}$?ns*oq2IR4uK277@)@^k zcBQH*RU7pidwNEYW7HgDig^|~WF*yA*$pqU%UtY3HDQ1mX*gDc9cA6M?>Sz&ics@*#~I@|cq^KMy>{)EjTrH#mANNq(i zY{|pb=S01@7?QQi_h1|E!N7$U1PF$d6Ll7~7xk#22YYSNS%M)dTFypn-4h9a41KT+ z5#PK-81O4&*UlG8mrrazEYQOy3 z$&}NhBv4Iux3i~jPeO%_b{RI>t0fxYVtDM?)pObOfgnW7a?T70eIB?c2Xl5quNrD} zN!@s1j*A`%$1)4cK_P%14aiS~M2*7!L6PJNwJiO$_x!$ON0SIMDCkOWgEV@ctS-oD zGH{ebB}X!0$quN?v+?3MwH>6D^*KG(a>ZJ%TFcb8rViCV(p<~YI4f2#%!DnJ6}y^< zeUw_Z>J})=A7tGtte#{-zow?sV~$p`Ip$2pZ^OzqaqXsmlPWuMmdZX#j5Tj%&*9FM ztwDYnCbvY`BTE-4!i#dwnlft&8lx8@SjCx6ws5`G)*EMD5o@Y;T3J+KbG1)`!o1T$ zsy+NvyZ+kj`(+>>aWLktZS4f)zK&c{S(hA4jWNlaAO$XFa}%4S%^s{Vf1{EQS>1<4 z!=JodysFSnPMkkYf2x9OWVd`{OwsS+?^j(1jJ&acw_e`mUrq~nU#+hLE{dFogj9wg^1Yiu^K8%r z3+VJ$)8UkvPwlz+@zvMuqWgXP6@ckoJ@>Oy`1!MG#f zjRCWW+6;ogPN2UH2SO9qCZ^Ls7&+0PJ{6%|G=BvdHn%dvW>%sjrpS#{5@o|j|F zSRLgUic~6l44()_Tl8v&3;|2(X2a(o;URb=6&pQH2g-U3VzMI>?TLWpQ_s#Sldak< z{}I}$7P~h|+izJ;nS{HKb*62vym&*prJODxeg`O}H4iM+sk>BXxT#Llb?i%&G%U1i z)G;70n&;Sq)-Stg<1eLtvmr_ll8tHVLy?O!Sistm zTjcYYEC!Y>^9`=Eu?*KqLIv@C`AO`9B7%(BK$u_fh34evX|)JXs|>Y&VL|9%ltL!q z@>in|X{vZtIu%xy&yM73J`mMhZ)q)NWK{H;^@PTHU)4^CekZ2ZvE><4fmr8tjzIr1 zVy!~dWKbM4=v*1-WnK`j<}_-XT2u*`)@yPT=_UvY{e-xs7rET7GnA02?Nl^RQiOh( z#a}(!X}b_LJv4c=xdlpbG$(Mjk)r6C?xJVmM$b%_x()hH4n><}ZMP9>1J0J(rpj#b zmL;^?hy~};S?85sn3XWf++n1Tw~yq-GR}HK3LQoS(_!efMRWt{FjDY4Xi{w9_^@k}m|HrGxhOxag36AH z?N6Z`77}KwDixr+R6w|?0Mm8s`yFGz9hDGKYb#;a6yQA=DO($nt#sb-`iB?OIraWX zUG_8b6pcDbSkgqIHcvaT2}-_22$u&Tx=B6BGZ(nZ^o=jsLuv;?YIQYjX@4QN*7g@K zLf`GcPKRS3Q#6k;4N~yipI34st$y}hO1JPpt z1?^>9_*`Qa)sUt5zFg1lv~jk$@)5BvMb+FrCrImoyaye8vF#2q&y`XJ2Nq61^;{`s zw8FZ#r6+l%ehjf42})CqOKf@q`FH9!@I6Tk`9ys^@XD3vh_u`81kAY{zU;4k+CC7p zp$)v?3{}U8e{Ss@h~`eB0|f`KNp7dYzRVvLZq~B-Wmd3JR)a9KFl)%<{{97E%@u-xW5WYCJ88KTc&aQLsJc|519ai3Xq#d;>ivRp9XiG%J$Ly~mbM`l#AXvJF zNyE+OJOG%!x#wp{10%1F@{(jF-Ky&JgMleT(m)(F52%FvLLon4NoF3`V=5>`*_acrSN9%Ww&tOj+=jS zFO++OHci6sl9)2(zCL05wFdX)J9R4n?(1J-OJkA735r)5t3Ya2QX&Xa4B9j)j|FFz zhF_=qNhFj7XqJWn&C;-=F49=S4Hii-SZNrvX%gN=;s*2Ov-CRi`Lp!Qd|en)RD==3 zBEnc8j4nh zzCpQ^!RZOgc5H1AzkIm|(ID7vziDv73OR%%kFWf|29Ys5JeFH5V<3qkMXRjaVRcj@ zMp&{KVWj+*12}$e0N2k;#pnPF7FQ3jV(~^CZvejD)I-J%RriIL6PkuG@r^wKCS$cu zNL))l>+@2+?h;xklp^dBQMWYqYI+?ETnBn)GtT`1vqTZcBz}L=FyVT}MAHa3enEur z30oNx$A5I?i3)1cJp4E!hyZb5Eyrjp7KXz{hJUg0!>MMW2(nz%gZr*M=-)!^Cm&8@ z@gx{dkEnj5?)Xoyyh22sd5|sGYJc4OFvIrvdSqo{*HpO_rz*GT(?nWIyHcdQ8+y8G zPkqeUA)_%!@MFYr^zSXmY!qYX8c>8SH5?9Nhh6rXD2E2ayy?A;g#UI++ZHi%5_`w_ zTjt4sjnWt{*3WAG_3xcO^D3H@-`?4zXo566oAZ!`4{Ml?SQ5?Ba~xKgWQ^FPs`lUXQ$qo>Z9sq3 zuOD@DRQ4Ck(|KL%X(ghSF3W%2&Ns$Xvr78%vFJFTcv7?JzNP1ak8{R?Nzj%8BiG*oW2y(68^?3j8HF=|I(*(KL#Q=U_Hg2`Pt z2ONs*2KcM)O9yXiZ{r$t`edUicbniyjLj^4?7%m`&>)Th5?v7RK%C?m*n3NlcOPT@ zh;v}(mY(l}cL2$*2D~}GDDs856Zn}>|F{F>Q=rb7X+#B|$1KFa5}ow8)X94G`_ePjV#b067^% z4s*bl?b+4`bABx-lScy{h-u9L3>*%BJ~N(Hi{S44gc3OmsP4;fFBgWVHExU1vV)!N@s9dw63;M{f2+{jOjq?MHVx26;OWzwvi>XY4=$t8I$-|J)v z;fdk&stYpW?UDScLg-+b)Yycb9R2BbqnzG_@JTe%m>f-;4=VRZ{xaCdgfIun0vf7O z(Hjtz5i&Uv)+a~1pu}%Qi<#7g+aL58teIqxc>J^!w3@XBC&;o*L_3nvEA%eEbDPlv z>>@gSIEfInnuLwn(IfcKJVAYS^y`tyDTcvicC?B+&5m@n&W>t?D995){z-BKsbWTL zn&eMon$n~!H>O7q;>iAWr$=8U4gCGB1yQgvm3Ff>O^=lB0zpN(i@5nGsRk=XgEmiU zTr?2Ujrq~NWQI}!lL3iI=CAW3DJ0OiViQZwQT&CRIo$k{9D^mtpv{wW5VomExvo!; zezDZ|?xhQlEj>o5NJFuLH65+B!{$k4M-XKjwE421s@^40I79jXnIR=W^PIqdX8!l% zFZ>_G%|H1!SpE&#JPoGK{MV;Q|Ej@%nIdKW>$3uBD)N}LK;$tE%u}QYf);}|Pm2Rb zmWOks8}UL~fM$6Z&@7Kp{6!vPxcR3%3|1ZnZJq}Er3U+Dl2m6uPm(hGb#X{i5y!CQ zA&wCsJ&B}55TqEiX;SJ#vnC${JO&9pYnp=4tM+QbU=Ee3(;d~biZiW9l4gQ|V%$z` z6wi3=mwdC?)7|O9#_=vOj-F<$SpK0rZ|Z~K(~kh#M+HZa^nJIt3{X!ZJ1RZQ-2MOhLVv^e$P2f$!>Hz z28!E#B)daVvo7X#(|Fg)i>>%@@2T<^x-^ul3$0JElWW(H&CtKt|92W3*Q zf^Mv`{DCw!XuhAm7(y2Q8ty0Zbe5-CC6}JkZoDFpeNze z=9FxSa%!;5ls?uoYkX;bqz_$%7!I)I%yv+T4eMs^A>Z-{9UK zJN}tCXgWAER^fE%f5FE=(>|PglC!%yGtA+=;jY1%AzEU8_3lc0v|H?T(bayMKh{IY zcs4v~Z_>R=jnh4OO9G2~(P&s*08Sss$E2DzaI7!qFV}iLH7@$0ty9(17u|o-C+jPF z&XblrUU`gwI^2T9Db|(XoRQn zKUe82K~(8`0d2J>b~$Oio>r8}YwE?`W?)jeJnfor4lGF}zodxIuKkdZ`0%@{jGvma z98YpYbWT29f}3j!p)t+OWGGECmJ`oPjfUmP5c*}vNSlqdXUE5zX*k!|o+eMd7yiFV zzhX?E+g|_VI#W#PB(SWybcTFGJc--%v~*|@3_4@U+`5QY8iZo0UUVO)MUGXs#2LlI zhM+jsb*|$vgB%%v{x;lZHF52KUYoXABPYlv#DF4=222)0uJtWq9?qC`4>h@~#Z67> zuCmj)ADEf$nEE1jUqm-0$>y|_im;)29aGQsdS)s#(A8G~E{IE;^TGz%Yi6UM6TqlREqsrYVbpIoq`GG;?FqB23&egL7B_b&2ebHqG=bx9v_L1PWWGcbb*O z!t;hPf8a4#jt#QVRSJWK6S>R;S-KCJ9EIt2`j*S|u0N)C-yl+ZPgoAZP4#>oAEu|E zb$ZwDWWeAuy{q9)(>qPja5#S#C`4?#IFWNPpAxE{@FI zd`=vhySeA{L}EmL4xr4HskEDQ&Gc@Lpd#IQ+?iB^m99aXC$&uP`uD{2&P#2fRKQG5 zVv^$@?%lZIlyo5fn2HBYLw z2%>C*HeYs`-XRL7ckdxHqy%W5&KS_l|0Mpx|1|E*zrpfv(B^44Nbbyk>+~-1U#54N z|N4AJnu0M^OE)Gd5;;34l9W*dc?;;3N4B9j)X?lmz?nHpjt1B|6 zbr^7k7g2W;Ol*6O?F14UUQ+2bbDOE?>5z?$J;A)dMRc#>#CPZT;_u4-fNPH~zUg#^ z!pG9^f=SXkPLyMC?YX4Rz+4`ONXM6I&-+`Z8*7U3oYZh6&eLPaAJtzUdbFe^-ip^q z2|olRe;K&)`~aB*VfEbF-R3?a%*M8Aqrdjl>m8uOZj9LpLw}yEZ#iM6L|8rpNs%K# zLLsp5oa|+~`6wdW&!pC0yKUSew6tJ6@YV5VlDEuT)KErU73Nv3?&Y&r5!#~)Y2LCy zvyf>BKQttN5z>02`_Y_xE^@aXhpIjN|0E77)tQiwO{h-LmTzgMCOH+D<%C^&DDWOg zGa7gZKU5u#aN=c_{IkLQ|DKua_lL|wyqD_RjPu{;i-CV^dr4b2?LFVo@>53bo_m;& zf8TZqZ;I=yb&p?^oX>kFM2$Z+duH{pE?}bPO+K-$?fS>Aw25*H*LbYF&fu3kR^0jK zkML8+Vx2#DTAut6i8?PQ~58ar)3AciR7K8-{})Ogx`{_q}-R!1e?E>9$V&i3>D&ibAp7 zv6BD$+l`8~0~ZTOG*sFrvuX8iGH1pU>kK)BnElo*G)xC1T!@h;Y%%i22q^vh1a5y& zVz3R?An}X>L7ST5nm&WF+cok!my40dmNP70O&)uFVPg&djrbUV1g%CMTPin4a53`K z?_}iZYBlm~q%j@<`6tN{1j*5+H%)Rj@+2pWyqDv^O48qCIf+T*w?>{65@=lWpx%YQkkgHu zf0ARc3QIT@pYUEkp4I@t)$|&A$0VsZ_c~aRCMA-&yzU*S;AqtJWv&jr8 z0h)~(1Dg3iL79br2Q;GP-(dNVF>ceEb1)Su|E)$|;=dSqng6;~BTYpfOBRegYQSvd zMG&+Yw0U_HBM*6K#4$%$Hu53}QViNODQWqZ-Z{>r##xyg+{1MxoBn&$PJOI8^Qr1t0n=BW z#Euevja}(5E0NcnTwu}p`Tc3pnQh)!pJg#M_2KOCssP8AFUyPe`=PLP(LUnS_r8Zn z>Uh#^(Y-OAywLHaUwSHxCmaBd1?$+ib%&hsBHIfyinS=ue9l zY;I1ZlSSpc!4-L5m8GWgU8QyrDPy&6)PfO4{tyE0ismhe75`#jFQ0?tgR~`59?iTT z2sJkY`~_uA?ujP%Sd)9SxGBeQhp32WQM`UmbeM>ur6V~5t=3?9GDs4JfS{@7-~_XY zH?t$3!F5ids93SedsO+-IVm(um@l1^84iO8CgBN#CP?}j$~KI$`T??90rJnjB!a+W z(59*SD-$;eT5~0xLUNo564@-VQo>!rSeP;m8%#mqrF8~P#ZCw6@&R0}SJFN4bSPK; zu*jh0Lb_DifqJP?RnWU%%IZdcuRdt&6EbB8KX#}7+Wmj%rbk-AH&GXXU=T=1H;_71 zuEtL0;3yaqKK!t0!Q7G*Yz`s#cKk&(I&t&QN-$VP4BBiZth|N{Q9cMP4{}yPCpqFJ zG`ytJyJ@r#5_@3dH2%F8G>%?ICrcT2vKZ;jeJ)};8YQQGj}pU6s<{YGragHN=_NYB zZ1K~>jHFI%mm3d*+ycL=XO|(^kYTTCJ+kgf$OIkTuIT74T+`8EQl`$uBVX`)eEI$V zNDby5r?sPt_@?h8<35rw3ZuszcZc$Zo^JOAaxYBJ+~s~^ckYklJ0$3iMc;v*p^NXS z{c0Sj((rBBCHv7TTYSUtO~WH$aa4+d$MT0daQ49aegB^77$BW1lz=K5<(`I%#B=ei-Uv>IKXT-1h3Q zd;fQBA9)HuFE}k9dCRzC>cUtq4nor2+2p0|6VYxBGcn=Otbp1^;jMio3cN0mk{ks5rM`h{^!>ns&7?T7Q=}zP3pQIYB zbPd`(scU8!H;Fr<(`WHO z8#4@PC?@2v1yE}v=1FBo5M>**`Lfr{FkVAubSglzMQuPc{}ud&|0-_&$-lw!Z_wsx zP~Rw2{#$1liT^Ug$o$t$YH2F+=(9lN(JzifO9VlSL7S&#hpFi}@(knocp)u7vpfuF zmPZ%@4X~@*$bO`iJb=+yI|~8%TRCTIaATonTqHH%akXK^8pNYiW+l3 z8qqc4x@L6uWE8GkVAcr9%b4LVVbF9)^o(i@Z496y8PYglxGU3wJh357)if2;@JEy3 zraNnh=Xkzvn)^PS$Fw`{!9Hj9#e_8UGOBkO1`gK6@_^oV`_S1}wAH>G-yk^00e@f2 zg>-dW5`P>@cYC!+>-U!4XYx-g@26eGh0^~6<+XgGa4LVb2jf#mz6#~S4ARzKj)}C1 zqVxMz^Hk1YOj4Mru6wX+aPPLT9})5{WgaG_UwLpSA+!byV=k&W9d*{yu-cm}jqIE( zp(s^t$A6bpbqq^uw46ukn0k+>sF}W8yRj1KK`@HZY7zZGphrFQd#Xl%In&U%grS^v z@Z3k_K<6Sy*53O47^uvIQscDIE0KfOX?Er7G%1$rG;g|7QN4xkh%c&G_X zBL<`CeqVrug0w<9fi$TjNo?se2o!dgZDJhNC7O1U&&7~uGT1WDmiz13iaqXa1mCkj z)J!vx%IOzP#jwoI|Mwzl*yFF>`I?j1NEzAP)K9{R>%2t2Juz% z)k$tN-{^4Qh4?*uSoJOM(F@@qRbv$ns6rQj;K1m>F@2!t#t3Jy56IvzbW8^N(>+|= zyK9}}7p4UpjCD2!u4;HH;}grjPli-@?ZDqZ%n7z1f5KGHE^~K$G&$^9MO{5Q^7>uk zhp1G*@+_W|`CA=7JNNg}I7CW%ML{Fuj}NH^u74e6#A+%+=kGi1t%){r=PE zl}i6n6AQ7|Z>fKZ zO4Lh;fx1ognEqF_t-KVoM5(lm*V8!Cmo$$xO;^d?9MfS8;*X9a8UU*mAd(47s$Db8 zmK@UQiogP~vGvoVwFktw$noJPbNGpuR`ai7m|E)z=2!e!P9*wuJ72%^4Ljes^XfMo z=i@)pgedcfu6yN24IPdWI*%eLFjCb^tqO83i!iycfb}euP7Q}ZTB>>9$HFq^Cd`AN ztG)H_zJp4t$@|btLZleRp}4UlE7ICCf84;(`9@5jF2y~kuh#YxB%%n7&T`r2jwr&J zJ*ay{P3;QPo+0y|XXBw71t>%p@^WHLr`jke@pT}pYErY>%h)nxG4<-KIa-?@?L3j| zUlY$?`NZmUaiOxLvqG0htESX|lZH{55k}8%m;>Nyz&vMG-6P#hB)86Uwj4a2F}LU7 zpbTt*h2bbAPaF6H%(A3-V9(7x=LbB{^Z0u9uJhY<9hA{E69h?f5gY>&@eruJDOvo(<)NOzVB+5+z#^`*YG9 z$AAeyk;wQ(i%&wP@|8jP{Y5d9Ab?x}`v+K>{L(99Z zxwZOhQPtgdZK=KHWzhBWYvM~wI6Q5#vyOz;)}Cw9{)Q6gqw_nWlOZAhPG3;%TRTz4 zbQj|siPXo#CPwELfc^}d%W!R6bOlm^L}p22KJC3p!;n^lAz8!J=c56088*^LHyO!6 zF=uomICvy@J_kqXU<)khY)YQ)QmO1=;fHkU?0o@m?)h}U13jM!cuUV`f#~e#0P5`L zBlt+q{X%rqX`4Fg^$h6ds{!h0($xc5S8vX`Iu*L=>!3+jk7QjvP+xaXx=KF?9vYBc z3A8ydXs#OSq2x#fRQLRbunLqou@1mfw1_?LPo)xWi`jD^h^p*45=6?nG!GCi4_duy z(bPR1zk6(Ij@9aBoBJYXaa7ZOxb`=Iy`-J(G3kE!c{iT0uqw#pcf-SgM5+V0wZsOH z(Z9t#F#Y}>uiUYn|29zBiFFWjbsAWmDQB)<@40>Ekuq$GD`pV0Ao&4Ey<;*L42Rd&3RL8CN@f38e5}5KBg)(ez>}s9Awb-BY)&RD_&_L{hy&?X- z@l@jzAu>2SElIcFX3qo-{ta5C*rIWk7~xd*+|cvJ0eb>F z7+_HOC;^qp2e(wFZ|J#7QQsOVX&yl0KL99IWsJ21^taYM-4s4q+{)d2&|sK{brQ_s z3|`{1W1R?iBwNS_l7&nI9U=U=mv>D4INU~0^#(fE7iHJWJEo32wjTbbj;RY8sjeOW zXF1iIJEne%RLeUbhq?^(T*by^`{CyygDY3s4?ia$q@fK&UYXe)qF7EuUjJyEll@2vgG0j{-$#M9<99!XNEerPPjt1_&pLlC^0<&el}%LlzKyQ@WlZZH z_EYqZ!Mp6fD&nE#B=~rR*k=$?{Sn1BQouaomm-IvNH;V}<`?hmDo2kvHczRU$!@a4 zRkTMyW}#HnoTpwULI%4zY^cvmonQ3rp%LDQ<=gj zQ-@_5Z<+1DQf7ovreXCmud__7q!9ueg55`+W)_z~OL1Sy#R-2{7Ij#h@fKGDmf|9e z;tZ=7_q|V290?Z)^rw5ixM5u~;|*;(o_E+U|YDJ7jmPJ5abkW@JzXHFyOEWs52UVz#xR0shn z+-UhZ1S~>`Foa-OT?qehXR7C5E7xqtIz%m}T&bc-(v!g6jBTc8Ti zDB31mT3$;(xdeWM^CcP2F#0S5EZ2D1tpQErXb@ot$FMpLe{xLWjRIb`po)kQ6iqOht(A4D<=wR)3K26`&h+uxiP?ys#r{I%*{ zLs$%-`BwGV*Dk9bdt_^M%jYZ8-(Dr{ngvgz5jh+b#U6u9%) z@?&@Ie&CUTnS1wL`iV;K_a;BpclnmNftkDUUbI?gxdOS=%S!>l`lUKBtmGd&hy#mWNrr6@(00j7= z(PL`lrG-ulp-BtBpPTgcyv|FPWDNbUS?gyke#^iXLYoL{B|$bEWjP@D3g%>QaCK_p z%DwGZ?j7bWo8itYdCO+!p3&~h_e?zf@;xKZxO~s(5ANJE*0XcZ#4|77GxV&>_f($k zyyOK=7DaZ&$@$4U9%)P&iF+XhydXE`6R_t(z>;iI^=**q5A8S`Cn;a(MDj7Xok)&3 zyV)M>u#TxlB@>OglC#INh;jC~HQ>j3wg8QIpfu1~ritpmT8VQ2QXYP@x6}_KZD!V9&%e2KJ2n z;J}_y&RUK=v$AL6Sp$2j=-P0X+7L>4(c_YIacEpmhf_DW(!N-aSDPx6%+Hu9i3Rp1l%q_9vqTuK7yJeQvZPM{B zH`!9UFAvWAG?REbHj-HZChs`^r+A=d>)^~!^`;s8&fBgfxHfUZJ-&ZR1vpVGWArSj z?4^GX7@e1nXQGAibpw5re%t7*kc6&0m2Nu1pgvZkilMUqwoUX=`^AO=rezI=Ed5%| z5x!Knn(^94egEh`rH^Xtm$6H>NSkz%mkuXRuetTf5=z|u5px<8$Kzu(wr;DJbbR#_ znJ`fTvOpG)1v6peli+k;R3yGT*4`l7E2AtkHa5YSGwr>C%h-=`;FD>ONSrYNOEb7q zB1p~D%h)3i*d8{vJLTPl`G0ja+EL#7%49m=8g z8@$`26?dKWb#|K|{5?EF!k7N+k9caJ$Rrzfly4}4sT+S`bwjpgtUR+R{=Nr_eHcmd zuwk<5KH6^)#Ops>1hH?xozDSxmfcjwfQyWZfFpi(;k@xTP*^Tu z$Mri+wQWnJ%Qsc0bUUtpR#V}p<0}=o0|Yx7<)4T5yeR+06oQrUY`}}##=Glv+o__% z6LsFFLg%)dQlB>2A3Uce*?QQ1qY0ZcB$7S@}0a4bLI1I4-EwdZBZ`+N3K zoJi=iCu4E>1hQ^?x7G)neAAYCQP|C&6fj0iqI&A$ger(?yq!jm{YL#v4MbPs&hGA7 zyBk1KrKfZJbq!B<$J8WQ8v&J$DMyIqzu=EKCZOb35!bKg9Y4ybc6^*LcB3zf{p~bk zCgrjhwnUz5;d}gAj;M?`=lTSJr^)s2N-i>uk!P$a&(9NdYL=Djq7iHmf>+kK|^iYj@a7};M{3BK}_utx<-fr_=wU*!`Ozsom$%7`N zR2oECiw+4jmIy3?Nu*sCy6}OBg)a6^k~`}Knh#n|*tp>ScAvfFmL6t`q%Zpmnl6h$ z;DJ~f&~{wNRLlc+=2iLExGH~lL~wE^ASa{)rWrAo)d%$MUfo*E_s1)h5kk7lAjqXo z<=D}A|M}+TdYHnO>*2a^wteCq)&p&^t@eQ@c=RG4eM88@VY0!~Yr@{6&(hf2Pw~R& zQh{UHKa$~4hLsHaGVBI2x0?lo9ppsgCfsR1=++ap;qPmY@ivLv(B?WxU}J0-!jKTr zr(hO$@FI0?1z#RJQyGoVI!xF=k9vhJc}6a&)1C`{Wct7k%gX(4 zfT`e9(I7)_!e*SDm92_>;5PdbT=3)>ngmXob=3 zQ87Jl3oP-uMNPrwwiuY!T!lJ(@GkMW(gKV9SBVG^H^Ap^W^nu=RXudcW2FF&pfTf8`_Y zU7kDPWU7NAjXv)r($S7{^=KXvXT?dYw2T-U9lh$u9x+$#?)+)SPU_&tNO@!AGE$Bv zfexDGy|^@JzPvFkc8dj2~dfW$n$rNBcdB^ zpmT(b5MC!c_Pxuh7cW#V=AEbes!tAWRtjpfI9BRI`n!I!c__IKUmi+c5zgiz{q-Gb zNM`_7pXgVQ^aN6xl1TUL^5Vm$3&K?OruP#)mN~?*>$W%xG+s9FR+$^q4o~8lN;@vf zx@eaAnbq>7&ze-5oy9{!-}~D966tw zzPaaYqaS_PpSAeC=bih`=DPa3sd`;~I-JeJ{^9c;KH0dMF0GxE!KieBFr7}2X#{Wp z^dXd8BVZ}Rg$(C1Je=WdhWj&|%y0~7`x^z`48_R^0O3$hP!ZlbUu6QWv-V=Yw6kKQ z%n&K70;wqTD6sy32jV0P0`nsWTivnmUZ#RX&y7xZ8Vr)Yfb69QMBD*$x{JP5ee!%Z zA3CP)RHm$&N^N6t^uL9q6A#ss^R^cgs3-|{riikdao1+=#mur^O-!b?$kdu>Et1*_ z3uLw=bRAX7IV}d|6#;m>^jlq)2+Gu(?3OVCmd^>(#KwZ|2AwDGQ7nn%3>hB!lTGnx zOnl)lwH2L6Mf0Ayi#BD0N!E)cwpcH{1Vr^Q_WvWv>edTiH`K6RVtAx_nK^9xWUX$! z1aaAVNrWc$uwE?U=i)ST;kg)fr%CwMWac~yx@(e|^C+pZne!;Al9?l`nmGZJne!<5 zikYMGhMA*KPjBWt3Z}Z5^C;=U%-K&V>RfbQC{N_U%u&imZJ^oAsp&-5PDJ{~=aAt? zo0f9?V#K$;uv~jPi@R1PX5iCN;(5*jndNK8Zr_ilgBdFRs8 zpLY-t51$Wp@%TBPVzk{|>4fyQokx*U+s@l6{H?t$mi3*XQamP9eZsn%uWtERXo^Iz zz55|dt{r@VzpMG1p~1b|uuKhN$mx`@u2`s$rb=*e+hZ-{pWB$p+G`=*7Q!FByYH-) z`+8!0q6o#%E-Rh!QA1apD|@yW$<6h}gL_E>2Fi>yeALio@koeRI)2jnZXzA*BxMJZ zu^5lRIU@F9-%|Onf0;;X;XCH-td2=0a*jW{@ZNN%)HRLwl<-BOzeu|8v&^zeY#Z>~ zLc&h)$_^^FC9OZO(0RTACr@&uBJYwt%Upg{XRZuu89o$W+kw7zoIfX?CnAyRIkx)P zKi+cjJ>R)wOULE&H$8BYMdO=}wV}R7K2z`Zwl6}%V9zR9z71RgennoEn&`(|N%AhT zwsxpH7k}XmSq@mAu{3Q&$d_2enrEh94XxwJM@BjM4)s%VydsGfQROiLHWs{m<%PA+ zUqt)F4_3ZR*}H9;ECVV+{^!M#>uN_T|sX0dcU7nxLEm*Unr}HDsi$*o`dz zA&FKvYLQTEm7yjS9_3V0?I_|v?XO(jV>l0-u+|UGesEukq%XlABasDNk_t+t_FX%( ztGcxmyH#)f|CLRSyuvuLd_z27DhpYI>+Lz!e|6?knluz! z{nU4^^!DoB3kXlPqSZ>ZTitGelxw>RMk^wU9vo)_@lkBA1NB8(yxY$u2)j^W}M zDvoM6_MtTvR1sfL!z$ifT%?stWnm;5J{=LS>x@cTzb`=Ja$y0vlb0^?_HGc?&i;MJ zd_UhOtS^QA{eMYeB&Lf>VPA?cu~-_IMwfprD{p6&_4?7GC8Levskctw^Pyz>u|iFE zof@<|Q_~$?UF{vYWW4UhfS|YbrW}UX4l}}DX;|rS>h(pVT(a~f>04Et^e;(cBdF$w zf3Fx_0CmQ|zQ0*yagMG%j-PZHk z<8}&*YDy*oe0b?(L<4&NJV(OBs<##cT=V$W70L!S({$GUeGCavo9@xUe|zoShV8zy z>-c`g8zdWw89R6mEMRRn?}*&5r$Y#Rr1o`tn3=osDqvs7MUFT7xT+$)eyW{}4k)Ww z)Kk<6GINN9(_xEdU~ruQgu_xdWNdY9?97X*@zf|!%j~{$v?0Irz3b`ePu*kG9*k6q zqdy!JH&pxK7W%v8w=ZJlp=OSH3ppM6P??9{{%Q+>!;H0gJSWAx%q*mur9KxFFkvm~ zMai`&8OM^2k56)*-sQmWD4wJZAMfO#a`oR;ACC3C6V>Z@JvD+rxDQTzMlxW-sX0P5A-+CKH~ zUuvo>|+@pJ0h)bFaoin-w}@=AcryKV4bYf zv)xui#5!x<^clMC!#})I>UDH=`=gfa?yh4TG=UNss$LY8fWG0m?SsFSR$p^I z!m6x_Y|Saa7QvH0iM^47|&TQ2@|XahV9#BhgUxww=HI^>6*D zy+KJimJ*O>7<1|hDe5(-OH+_Y4PmrpL~R+?$Q!d-N3GxyRfaHj-CV%=6)OCdU8RQ= z5zXCR1sr3w-O%EK-|t$-bg3gR?r6NJ7w`KK(s-`#f|X7&U?O;@6ONTPth}y4TxbdA zej5M&U-sSyKChz6|4*Tn2(5XFs8RXzsBt%4bqy}ve8npGB~Cy^artBw=TFl{oWO$a1OE*FEUGZQh_)I1~ZFNOKv7}<~K%1n}eB)8-FH=zW z?^v?A=S@_3xiY7{)0A14y}RM6#nnQu`(^s%2@O~Xs+_=%w#vJpCx{Q8e;mqBu(n%TAy zJ$de{oz*e^iOOnLjetKst;mbe zc&kub;fL|E|A;ZDblxWsm%Bdd8wJDUwuLFZ40C4BBQV_Hyk80)45 ze6k*&i=7u0Smd|<3p%N{Mw*}6E^Vu|A?*WM`%KBx8gs?esc6JLpXtZpaVK?etpeDj zro-#Dw+(hIZg)sB<}lZC@Za_Knz{EQ0p$s`=`xb1ri$eN#b>4;>5@*d z^tSbDj%pt~s<9R&X;k}qd^v~nt}E0R#n4=rW-i;IL1{U^zdZE9+pDYp@PFnz=P<+b zr4OV_Q@4d$=ef{?R46DVT|-47RAr%KV$oxvlh##x2!SxJsuukELhI4r;-uB zteb7wBcd8okt2B~jg{#-+rp|efF~1<5gZLsVr+cj5H&c; z$+-ftERm^o6f>OIZBrAG{Z|W!xa%0@&FL`b%@$r&-1YrBqp=9V&ac+-^s}RK4%UMpCQWXEaV@c26|{S9-k9jC6;p0>5lRbX|T5FDlB0PyEfx z_4MG@Z0MEjrd;wpP}B0Kl(@VuE)U^LkMz=?V}p2p=g-tftCF|hawrXYb?GlOX4Y2e zB8Opbyd%RcDZ_D5RvL|&%4iAW*!uMaQ{o*qo5VpZiC%>1db*w3KVDl}v=RCV zY{QB@Xxk#1-+>v|B(>6`qgKJj6lGp8FFlCZ+}@#yXuR5@>mWYOD1Tig3J4CJa@Bveq6iq;QRe(Wfw5#xM6YCAl}tYSz>GAH>$eqKu| zTHpHcPYP8OyIRs3jGH0rJv`OvCsp-mR#TCr*8JcxNQ`=s$As&YGyPqL9d#kzC{VQI zrhyMA;sRxi=P*S9Sr2mQ!t*|`q8(evsmD1{ep+;I>Rxu0)uFJzE1fr6s8Et1Rl@YD z)T_`f#O%DLv~80AQ1|@q_UrStm4&N>o8h+;+-At+D&c%g4tUJq5nU!EOv2d6^Ko!%Eh%h_P**KJU z_bXwrn8lAhvG8LrHdA#s+^?ofZtJskcea0qt%^nbPWlXbhP?0x!YWfhyj6A9{z-6)pjdBt85 zzQ1aYetE6DwkNgZSJFP|FLAi$1V54z?z;cHz!K$NX#Oy%`q(L5@JH)A`>v0OP}OU4 z{`g!2eSuOZ4=I-svBsG0Ki{`!Lr>nBd^;OLZ$F7KSxQ~IA}YqWk814y+@j>{IE8n` zhxeQ^g1=|c`L1_F((4J z=Wh$-Utg2zOJKkxA^*uks?}H5p4RPduGv>%7D`ph8y*%3n&-?H_Y;4D#RBZ(fa6at z9Dgr+-$!}gYVo9wT6sEE7}*Q9Jx+u!S6FSnd+F{jsy7O#KXF!}J3^%Ck!xbPn>l3< zHjuyh3eU5-Q8-h~i;C-Y+q-4zjT{#3UUc!Psw*mt2(+bgQ|XYV66b45oVzJKx2>u4 zqDoWgWt~mSU&{Z#WUt6`+M1T1UDm5Kc$3EUJory+*AIBFM`=-0iF+mDl!#j*O^LKC zuC$}J;)-yxnpXJePtyt?`Dt2_T#XeTLaO0P96Dec;9+vG#f5SuvAKL#y2&gOHo;l=7SI1SN1p;40|bc>*Q zdfSoNUZqUYRh4ah{jr{nJ!@$KK0dnq&2#@ zH#Fz({xD)FdU}82PKiYp*cnf2BnemZeB9wmgn1kvNM0kU(tH5NWdbh!eeJB^y-3zG zd`+4u+;CJr_%H=%$qLV1@zSZVaa~d`V~K_rhsBBJ|F~R<(m5sEX1gw&`sJp$OPM+W zC?=SQ{ErUA8Psjw64Dd%z@uxyeGibaa|B1Q}PBm~n zXZ>ggcO^h~XhAiNKD1jcJ*bx=C_qdRi=`6XMiE4$kfhfPQ1f^_g&r5BW=xClt#JxGyZSYlR0 zWtf3G8?zy*tbnnawKc_Y8d+jn@ET|-Ct3;~%T9km{T{g$ptPUyesM_~zs5Va=&~YP z%&jwj>(`bb06FPpK^0j@r{;g=xz*dus6^#ey!S{(BN^B>!dpBmJ=hzy*4u^6k+vp$ zyINA@1Va^A!Z##3g8>LnL9^jguKtT8e4nQ1n?XtVOXD(xSJeELuS(w7gNa3l&{|p! zq0MWn-`s}K%0t4=2K65Mj-j`zk#-CXsR3t0SGg^ItUu7%7!*5nkWpsX#c9#7+N|lU z8EM6Z*-$Oagc32+oP_E}K^``zAcxipl0RQ%ih@iKHShb572g#(M&e@qqBEnpQ5RO_ z^_6xU+oaav3ir}fmyE|rN54%wOB)^2oLk{KHlPltY2=RJo??)C3;45>uUO41TJYRQ zQn_ett@(?>)7LX=;*CYRYf`bd+GvNDT|h~Ea9nlO8Re>IIpstbG5_M-t|J;|>JniV z>PF0<;LtnR`VXsje#P6&vNn~f2y?WowN4XgD8;2}BHqwZJEni)B~$e$HGjP3KOmF( zTlJqZd*DFWi;O#`ysL*RovVkluGPbx-K&Rtd(IrLVC2Q3%GR_q-U}r4yS*kz%SP2V zJ+IJBhCbWJ7i4lBCVJKiq+r5-*B)wgqgZq*R-)`zsE~?>fXn9y_|xZB1>EV&5Kw009^dIC;1bM)@`r2+fL>gb&PpR(!xA-T`d2^HRh>!O(SPgRjP{~=_YOs(@c)q5fA3@PM3Z96;ym4fjkH}r;(+;7g37XZ46JeewqrkBK110OIi|~Q5tm8shsEc#$Ntr z+3QFxfT+pKVTxt2CDZFcIug~bVHgVAs`=f>K|5Vq7-luecNYOWvcO zpRe29ZvT$P1Dmwff(FXKQ8fVh(CVJ#n(?He?3$ND7`>7%c+%MVdRCExi=J)87Bb2~ zC6lgMR^ObTNkMjf9`BEZgd%_V%ForDNV#&AhO5f)6MwU^T?s20UWRT`*4`|?{6SKX z`28LHBvHF6LlKBB2j__^vory38u*}Y)yKi2tA>qbxnR@|B`6BU61*RiwyI!lum4N& zCT{GYe6H4@TU)~38S8^mLp>V!eKXeTH>Ta{8&Vq>XlYbR+C*?#cLTM_N?ra}4^ZQ> z#KQ;bnX$#z|zVU)0MN2_R?itcsT?wCs zhg$hm&<&}#aXhI+_a(CO6Th9pq?=TH`0WTff79=>hdwKsAbqTciQoKrwHLnCm#yjw z#jEIrw{fjrSp14!_?>T|nUcqN#4BcQuAZ4}53fl^k_Wc6r60MZ=2U8CS+20v!z+1d z%<9tW;j^3199~&Ib9glqH5-MDMU`8t$in|ATIRB2*VG$J11R|vKX_(bhOtZpk1TwO zF|K7SGK|7lUOIxY6f&ZmO%cE5mPuyW34OXN2w-n3-O0)-nsk`I{tlHlX(Aj+Lc^KN zA0D@oDk$2ZvVo&%vgzT~ZP<0>RD_4RnZFjfV-gj$foMl$74dNIB!jN=6_JsQ7pJ;m zd=cg=lf8EO1~%4e@jOj$5m~zOCDXUzCDeJm0<+#R_6|ZSTZ-`0`g&G@jz$l$0z!!?q(XOJ(KS7#&Xt(O1 z2=u~7*b!kt=h&{QEjbI-l7x9G>b}f#oO?3QElP~Zj0K(dEs^16A3eL?Jl_gR=J_gL zhIxut(U*;KE%Ow=!aQ$U@4jp>;w2X^>9=Z@Q{1eYWdX|5W4Js;jUUoPKAaUL=G^`4 zp%bP!Se_0^Q1cc2$vh5I%8+ox)>W)ytQx&*4@Z3Tq!jh}#9YyxwdSAHzD8t>lkO9-bQY?Wr*VP8RLj?j=2Mm_ zH$5f4^g2};!;$uH`n<13p@XWwfj953{_a|;yo&nU)=ze|D*O;Ys-%~yR#abFINH0S zjyS8nqV9M`wW7Wfm#LzjI&iO6RL!&y@()^T8;+P)Yw}L__D|LHy432rvysC840leu zH>iFrHWmPrhdF#YP3Ej}-K0w1oTgFLDIIu5wFaF^bW(I5%lIp<3?EesbY`Ilm%PY5Dw-C8k5Cun<=PlS zYknuDSp^>6lSi7BRpp&IJz1UaIRI1P-F;NEOogdT;Y}2MQXzUAJV`Iqpq$}#p}+|k z(1BMV`MULbRFa0wPXaW#PumC%56?jPTw6p@hP)ICGd(xosiYKL9oNw3FwAo=`%2SB zwxyr)hNnwow`D_bP_Nt#YFhp@t$#f=F7MOy$zN(Sk)|%}C>opeGnyW+e}8O9Fs3!; zec1%QCtSPca6P%k^|*rTtk|6$>H zueQz9=M{Y)$_pE1xkI!%1Ldm3Q@y1;PEf0-_(6 zV{yUFR2|ZFXEw4m;^glMwIppjGOU#*>F2~vXGrU}rcGS00i{h`FO19DCa%~D_Vv8= z%htO-BD=Yo_tv=1UpweppdIZe&r{Y!o<&VRJ~E1$erQ}$(@`g%{?c80Xpbsvz8%h~ z(G@`#P&S?Pvfa`$HxiMhWqmfI2-UOgwMWgthpc_ju1D;87|GOvSt)?63bN~d1c^n$ zRfVEU-Os-pg_~0@YIclUMa|L#SS$O$>Uxb1f|5pG5tq{Fy4yJtpSrxh)in*ox&S2zvtg|wV=Lwq5wl_2&hUv(4!O2Q z|3|AjPSUIxaL?Vkq#;3d;F@ao2Hog0z9_d_ciXhhBpFIPfE^3I~s5@hWZ}j zrV*ziPFPQ;lJoO2p1QBBwu~n+w2W64-OnV+fhF!WR8hVf=6@j7~bY~Y?^hiUCI;BqZ=+ZS0#+4vifzfC_!U>e=UDmn07t9 z$R92N*TwGmx&DSbI-TyLH?OGt?Q)7M>UOgHh{T65*@2f_)h%Na>!+yeZ1`=_lMSCZ z^)$5-re&DobzI9y@8)_-y6%(_dZ?eb=swp7$KYBu!b5hwK^92%Yb0E9n=wDNie!HM zT*~}3&gv)g3x_@}A>;5jwX!s}Kdb8}-5jeEGpgu~g?n9HUu~6QFrBo%i@vo-f`?$` zv@jbM(4KD}s42X<{@A&zf!#TG?P1?Wv0iI_usaGytjY#+mNMaZG^4WwJAPv2&_rJ8 zi!;&9Y#wU9pfHHTXalNMs=66#UQB1%yi13e7q80~uU%GL9&zz~^&X{&`q?j?#GAj4 zgm3;uR)*&KpT&NgVeQF6ipc*enqI@um-1&IfrjhmXgDd$rO>69O0*_7<^! zu-IRa*w1f2@GkhrSno`*v^VCvUVR{0{7PA{YTb|@5n0U9?-m;Fb87lUSgEwoYbnpE zS|rtm323n&qjtZMPsr@={uTrcRr-A89D4|(C~8nP(wzUo*|mDTaDn_<=jL}${GM}Y zh_`QkeU4C9z9+RH2`X5ulik=V9Uj)T^rXhZ!&86Kd@Ze>NbD>+kCh?4U~9qy+h=4j zSBJrLp!1}gel8c(-!>iC-j9Acu3vwjw=vGr&^Y;iIn8y^ z+NPqp9r1U;2B8{;;thXE-mG?*2O&dnS zqw+hY_2Aa5aJAIA_Ft2aV$bZMV@GfKL-mkYl{v4!+E4#S9SwbMX3=tdi$z5@#nA>m zo%td{xKQOh-rAW4q-y(h`J(Ei{3utoNJpdmqYKYWuMT$Ej?Nz#Wh+X4^saA^kxO+^%V9(X0~Y%|Uta6m+1a5t_!= znZ}rx?1qGn7;2}|b^h(uPQH@>jqj79Xvy!lu5EH zOS#OmVzV(4XaPY`(_Gy~X7NdH6C2{>IGd#qy6)>Qa$7?SR$3~~1gYdEk0~`}8HYx> zM)3W)&M!YlnKPGH1Xyf=BBSC-Q#*q4Py)J-GF_-L>BV14?y~SMd9m4$gf zYkYk9rl>#ZV2X74q0)1qs09i-;IFYD&nAgS%%I>=VU#XheeFcE3a${$k|(~7buJOK zU@4VJ@JI=kDQH1crctXl`9B4Vy=m;adn4Eh{jh=3_%;*fG`x&h^ zYBI`_*vytXxpa&c2 z;SA^@MyD7()JR|K;NHZKQ_{&6**gt^foTg3M0Kv0`wo^;=sY96DS|S*#OO^%IK1j3FEx6pQNPj4jNWK;zR??uUT*YyqgNQc&gcT8*BV`D z^ctgHqf3k~GU_vWrO~U6{@UnOMr(~OHoDkotz1-*xM&}#7(dcDH{YEb}y40x0=uJj1FA$@=nA9H8@=D?I-?I5U2F6~qt6*_ zG1_kQPev0)A2Rx^(LWn~#^_3;PaA#M=o+Js82zi!M~$vF8aKMi=wn9PjQ+*wQ$`;* z`lQiTqfZ!p!f30}CyhRC^eLl%G1_MIF{7)D#*MBv`l!*r8hym*8lw*zecI?sqt6)q zv(aabK4dgu^iM|HjkXwl&gg?i*BX7m=sKhK8-3pB3Zv_d{t>il{nnNnfcj4Jj-Xex z+!*xAmYbmTum1+2M?Dtb9`psHV~oCNbhOczj2>$AWuu1}O&UGe=w>5*3kRNGG16`` z&{vHfX!JFs2N?ak(NRWUH@d&kH;j%n+G(`h=$l4*K{7df%c#ld+eY^@`i{{uqyI2k zYV=(rtsKJTdq#&FO&Q(S=oX_!qg##iK4#MWr_p_kzHhX|=m$oNjczkK)aZ7jLyU5x zMMl#`4MsmS+T+^ZW%OI4-A2DLy2I$#Mn5w8mC=ulerYsg^b4b(7|j~pX>_;IPmS&} z`kB$sjec(QGo!nVerj~L(Va%KMn5t7h0%=BFO7a|^edwu8U5Pm4x`@~?Kb+Y(JrGs zMn5!aU}}ezPa7>V%8d>&y4~ndquY!Y8~wm&iP86s?ql?yLK|BS1L=F`jX|$zxi3_o zdOtkqhL$6OZfsc^baTrx==#kq_k-wb>P=9l)Fp*7{emE_KDG*4P7a_8eD6r3R~X&j z=;bM$(fLLXFnXEM1C3s4^dO@iqh_O*7(LkNJfnvg{gu%}jm|YX+UUhb#~8iH=vbq_ zG&;_x+o){xLZgQny};<Xa!{`K~ryHGU zbcWI6jaC~y!6-9oH#*(uB%`Mpoow_}qbC}zGJ2BHX-20QJ;mtBMk|d@HL4g@j7~LL zY4l{Hrx=}LbehqVj8+*v(dem0CmTJ@=p>`ljoOVeqbC@xHhR3#8Ac}>J>BR8qYk6T z8J%hLXCN)VJOilZmo{)Lp@FqL)0%Ks!df??6~5T1KVo#Q(T9!x%IHd?^Njx4=p{xU zGU_q>`dgz{8@PNUZuy~F7BMjMUZ zVDxsQHyXXosNd+VMwc3m8okNrEk^5%-fT2rG-7m_(FUWxF&Z|y+-SYgpwW=gkkO#g zdZWvYhK>HlXoJyZMk7W8MsGG+XY>}MHyMo@U261Jqkf~e8NJcy?M81f+GzB8qjwm+ z&gh*+uQhs?(QAx08C_!ZZlgY<_ZYp}=x>c)WwhDoVxzw^T5I(8Mt^PeUZYnUjTv2J z^gg3rqkk~E(C8nHeq?lo(H%zbH`;CV0i#_;A2j-*(H5g=qkl5WjXq>_yU{-z-DY&9 z(GQG1Z1jDjj~M-@(MOGLH5xa%#pq*3Q%3({^gW}G8-3SktI>ZLeZuHFMxQkLw$Z1I zzGbw{=$l4Y8SON>+UOfb|7!Ggqic-*-RRRsUo-lQ(N~Q=YxEVP38R~hwi`_vea`61 zM%NmB$>=(xFB*N`=nF>I8~vNn4MsN^?J&C0=tiR*MmHJVVDxWB*BgDo=<`NjG`h~{ zOGeikec9-9Mw3R{jczuYF#3woXN|sU^ckbC8GYL5-;J&@`nu7-8hyj)YNMS-R~dcN zXq(Zuj6P-bZKF>beaGk%M*m^7)#$rMA2<4*(Z3i?8GX#?7Nc>aTa7+y^q)q5%Ciwh z!|xkC#^?t|k2bo^=ut+u8~uq=%Ge;8$HBm*66`Tzc6Yx`lZo> zjDBVGK%-w9J;3NUMn@U_*6995dyI}W(wvaF!Ez(b7eOT>%_TuiMw(}W?q{SqC}^3H z=BJ>gMw+{Vjxf@^7Ie6g=DeW$8fiWZYBbVZ8FZMD=Fy=07-^0TT4JR6H)yeu=H{S7 zjWllu9b%+8J!p}U=KG)qBdrC1_SinPP5}C?k=77EzcJGK0_fL9T6+Nf%1G-HpkEqk z%>wibBduqEW{tGg0lM2r>mZ=JjI>4q`ni$TPe4C2(%K5>r$(C8=}Xsl0`;Zqp9JMy z3qiNG{1~b)T|a?B4sLFFJY*I0#Go5nP6)c8<#9m=TK){GPih|@ls%u&!Hq4Cg|J0H zPr}LiIC#!`zRqXTv>#|BJs({+!Pb~`?2yO4Gd%H(N5ITUXVOzqg%zi-;`)h;nnqrN zH5QN4^=v$D;9UEm>(U$3bKxm?#GwFqh)yIrp6Hl(lkXH%zoydC9ZjV*J9t&d^LMam z{@fjHY+>0^k&KF?0|=8rHz!`4;taf@MZAJfVy3f0Z%$sM@fj6|w;mvblJsQ_@;n+2ST9nRje5mN;LZ^~(0w9u= z5}Q(DyS$56Bz2aHlL5D!%$pw9YBvdd%NUR33H8D^o^kWsIi7iwpPqs1k$jgZ@@A3m z)|N`pjV7yleLw@t$>(RWoxm34F zeCLm?x7bgz*ER`W6QvfyuH5M#au8=%v74c!(qTyriO#TtPv1ra<>-jv@c?i%!}h58@o^*VXd;? z&rE)m9WVU+PNG@vG4d!xS{1jqOm7@$@FCes_r8lw$jE8%(7i%YYUq^qU;r@ zYB&={XPd}bzi5qh*OEv-S?~nc+U2ng%HEBwBDD1g<8>&(}kYY3_hbV#` zOhwjrbmFEKG8S6pq-DZe5>_s{KQ2sjlOYIe_j=O)gf1y&uWnI~Jy890-CcLxRd?N4 zT*Fe=Vp$O-O$Jq)ljT93YqJz+{i3z*dXq1AjOj5nHJ#8y(arD1|0 z60G}Dw7|3*;f~?6*zR>!vqUfJK3TUx_2VOCuW61hn`5B73}wmlL3G*8T(0yb!+BpD zFT=X(Qzt*u+n-45FO%gjICjsTFo0lHZ0t(eP^wmChj`6F@h3$og(-Ui1JBz4hL{b6 zOWYYe3(=`WClj3rkAt2an!o!AV#;xne2ba#q_zcm_kbL62eXIyDeq|(UW>I}NEr4p3!B`6jP zRd>-ymlYNo6vs;8+Hjav%B$Ou*@t7B@ZJ4!9vdBO8&;?rN1qgat|YS9V9YobnESID z;u#udpj5NEeEoQbBSUZF4Hfy@2VkLJSzfQAt)OI~?~coce9_w$u+XIsRk`VNCEQ-~ zne#@|`yTW@P^95t31=6M)>)ad4rtK5tl~;XSah7Gl~W5x(=fM&_{D+AjrO@HVKGRJ zszm%Ktv&&(pFT?HH*l{#N5(!cN(^bd@T*U8x!n)(Fh>|>DU5Wzvfv&G#h*36^y-Ri znQr`JI?T~2dAjmrmJyvBnQm0XsY1q}j+Snkt1^-#-Pyd)8BDK6__;Es zmwO*kuS7H+GUv)u|kQV@U?X>w&*0P7-9`k1-yw!T(tzG^QCBCK3IFiVr?TR zgL_csprYu~)ZwYGE^&*E{!NhRvu6^WN^~;OiA2W}9TVO6D>2OGik7K}&Z<#RR*{0H z*GMr$jDgi8d9BgMU&@~@;JK#dN&2;yVwBut0aN4bZ6i&W2?Jypu-mU8$w z85{9cM1Hzfsr4&Lp53rAI;(VQ_xhD3^&zSsUMBvriLt*9mA)W zql2%Mn@Wc@mCoX=#_?w3Y=o1xMY(duYS@KIt9etaee2!&K4+D zLG{~@4W~CZ(mX?XA9%Nzda#kw)Hv0ir2{Eb<*$F`X?&&Ozzc>{3pLCrSX3hAiYzEs zWI5v3yXC+Ag}UtYDhA}}mt4pvinxP= zR&Bam!Tci8`c8^@#77QO^^%irw=R1NP#;w2HSA5W8;EYTzaf~7auMxF0sKN%g~4eO zsyT+zXrdFWRxlz8!+Ja@y`=nCtfT}rkNvK#z9q839#XlcV58(SP$zfRp88{}ba2WE zNh0TcFRY4Fem!s9SNjgOXg(KvG%xlsKuKCk*BI2&cetA7RSQg1By|d7A%G*R4n~71 z%P3GSSZxZ*(r$zgr1z+B>=8`fy$~&bt&M0b4F=^=k?6=3F?e)R<#?KrptXeg^X< zQ0g3B5|^x3FP0O$HT6S$ua>nVYD=1&4N3)yl5g zGS&Any^aM6rPd_#7afvId0l#)8okaRzhLDHak1`{d7k`k`Om{KN5~b ztm17c#VEeNY4CWYu!Kr237?%L)T;vPB~0i4=>ivPn$Sog79-M8*^6iM|9qO{B#@Wd zK&VL;FqG*fe^oS~P zH)(qqyt-1oMddLkLuTcOIyJS4GyP0^t|ExH`us9T(^L4^m}VF+p4Idr%bzCUDM>{$ z$x2~DYfsIFPhg%zP`+3tBKx*5oOyGweTxut8RkBuY$fXuyo}(j`w@KZc=NwPfA4(i zJ?Zbd**}>6Hlg>9Att=&yb;?yN^U~447e;iYL*c;$`0ba7j%*_t)La!aEm@k@8_Ct zlwpk2ZEsPxJ@D!!pKe)Gm)P@XP0UFo21Xfqku|sC-963v77doUCrHy%zTs)FEweM< zeVR3!$uhIOEi=nsrteB1gTDE=Cu|tj?c`J0uN>-0h}|liE~IZIRoi}2sr_HN`+v1Q zAAOJde26|j`1{i*>oeFSYcp%LHWP-~;k6PBlQ1fCDfPtSOn9`iC3@RWJjG_wC9~l5 zxM`{F6eYo`3Cqvb+R|;&9$*&HwHTw=7n`;$2{xT+kWxixnPJbOx~OkqY~2 zt_sWK$ywS4P41^$w!)Y^;Ux)90CB6e?JW|| zC^24vTx0!Id~pz`Ho~gTEN*qM!mTbV9MMMUK z)0s*SPTmYkKJ{P6WkddNv%ifge^1J$J7p7eXdu0F7lBl*-@Bqtv0`&TNPG-UJcMyj{wR;xMBk?~U}$nKEuhJMMX-+pC` zLZ}KA5gErK!T!AJh>>JeYRR~0-!jfw58ol~j0BDJ`1mUE9x}bS3WtHbwd9t1*0%iE zQxsMGBEvvyQDbXQlXe?bq}KfDKXz5c%l_0Kx4E7^{y6Gs4URmO)huO{hu#|Hy!1K= zz+j+^Yw_0+zZUm~ORI`Y@oUc;B=TZXFNUtlU#oP8*e((IJYahD>sE48 zm)|BKT9HN`NIAN4g&-TA8RJtH<5RnCG?J{OWQx#8lG47Cr1y8~{cL&%nqI8+!{d2p zEQ2cI)LzVXc>ZFEvyLfCydP07+v<-TF5T^x#AHJ05e%(bFlr-O)d(XP`{bHwY5B|H z9;!pcRbp`cs+QB}a#29AL!ntVvZkdyf4Jp10Xa@6*4@@#oPV3IFCgA3O128BH`<-X z+wLBus>mXbZ0PR%=Ok9Sm6hHH`XCgrifE_RqUAR$4j#n%HKZnTITSqfG08dj)}#5$ zK&rxBTgW=MiY}7qi;^6TNsgvk29o0ymLqIUax^U<$7;*bh#b6!Z+~)xt|G(5Eh)dl z=H~aCHp;Y=+H|oGTOhk{Dar{g}?07%|1)_rNdb3&|Mxi^VxMut|j& zfqV43_4xvCrab)>#b!!AIhl=I6KkEtPRcVLi-iyt@Rt(09CEM05kQorIdn)N~Hjw*tG{9K+I3zQo`*ujgDgW+>S}*HdVoPbX_#szM?B$S1N{!*| zL6h{vcepj7wZ%5>%l(jswCm55g`>bc2ZSmgX-D~tLHA#sTCD3*YNY54>Zc0_SA`2Z zPvWa?wIY#Z8v>btauVQe;;F9#=mUPm9-!CXS?`X#5R}}JRdLynKWXN-@zFFImlxsH zVA{nN_CdjI!qj3bv?1 zAx+I`dw0kdn~mOJG4oe_UE)O_WA^#R+ZhIUvpG&mc0;96Ic7KLZv$S1x<9(3v`9&{ zt&vxyk%bmKMI0$R4_d6C?$247iI`~@71wfB)-YVDiUxI$2dWq}<8RUdU)NDprLnwe z;2T6rD)60b(S#JHNa~l0I+eC-TDh4GGBvGy9F%NlQ(Urt?BANcW` zLG{CSAO53g4#{>GgHm&tzN6ZgDOrBf&wpzLM2$+Q;6^5!#0iCjWgtYqFhZI-JYKRc zf3%mID4(5E`Q2Lb!Zhea($uU6-S(!v7IZjj+!a-ZIr)iAttGz;32QC+l8`t0w-;FS z{8{H~T-CJJ4;1urV6FEfz@@uAm_~l_QId@<{klO{=5qL1yUH1u%JBVSf}9>tK6^rn zmF1z==R@>Uo0bu(N-|NpZ{ecvS{v^*RfqyrBZ{R1HtJ8&K%G~2(Ti(d-G%B&WRZne zxAu|t>IMnRJ~Q!+darIL_wed2EWA1$B%b5dMIy>I$_-Vc2w$8KD|9{vl~=b#7zs6Y z>8I;b-L=HgPYt}HxOjCMVNv7>B7VvzjkLyHye4SXtLsg}?6_{idIGAStUt0Q;S6E& z>IQ>aQh9Y#!Yk4W7PS!m5HAK@X%l<<+S= z<<*sf+9f%-Dpc$2RmNgf_1$C+_T9K6<^o-EB<>Ey4@xaPqHplJz^2WfUq< zzOt-lqbcz|lv)foW08H@E4yE%Zi$%oG?iLP{o!LI>|CYRt%t4K%`)K{mR#^=+T_Tz zXCpFQ?6)GrZ&^Ntprq%uaoLbx zc`k)scU@Pp{-gam?GGbtleX(rQ19B7of2b8`R)|oDdjdJfo3(XN_SLmaY3xv?4?9# zxamDl%ogoXyj*E>^NaFT7nh2nQ7SzOA{tk*9Hz(BT=AzDHC$8Ohdz^KWeC`ajkr`(+p|Jq_2laz4^&J56XrW5D%NKcP6AoiTX2i5sFksl&X zI#OylXjzgJJyO`5?Envxa-%!YMVAL_b!`-=8@7IsRpUTO-TkUwfv6rgYI! z$X3=(FKYValN$3Y54%splN#1O#%1AtXSQdD@34C zRZeXUO=&+jHBg^ZTjcVz$mL>WjQA0(;r~Yj+DE+M{ z@l73Gcrl0+V>MAk#}XY)v|mZp2SfGa@^$Gl+{hFy&ViA>CoDNuI}4 zqLYbEBs!kxSfZne_9xn%XlJ68MB5TACt6ChA<-EsKK@YbNJgYQYLsJ;P{xy4$SNx8*Fn8Rw~A<<^HU=fOn?*GYL;6oF}Z2BBTWqJ+c|PvSlZ*13DS>ik2NgS-1$g zwPhPr&l|@B^Slwdiq>&OP#@n~g&3Q`G|Ej7xj>^qnFI!9(xltAdc3hP{$eNh1(M#4 zl)6$CZj1LXq8k{46p5kaikX zJC&l7@!5*hIkYJ=%((*9PrMYCU+@!~aYUfdnm(Zl#fy1$6bjWr|A(lKMiN?}z=NR; zCKAy6iqL{XcR_ zGj;eMV$e9esdNs5#&a1oUc{jBG6s!T{y#EkBpWT=@&_F>@RP$3vgi;8!f}^jbS6BN z?sp{|-8uJ*L8BN%deVIjBHZo;(mCkUG2#|O3%D3Gib0|wnPD+V%!&)~BUj?jBwP#< zdBPzz_ou)_k8J+u4H_ji%|GCv@$W|-+@NvsoqP1XpQ0<-Lgn4lSn;;}UL7lb4;tIo zSkXt}<-AFR|50NF`%Re;evN6!*O-s|I%-6-=S`ZP%)}gfqTvjhjRKjw%xL~IrJFky z#-BAUQ=B%f8*f-|?xC1EZ^}DYG|^<<$wV`DcCas7%LhzSX~v<9nC-k9tm2`RhUsIG zMfz%2HKD%w0{!_{EYJ?ICd&y?1QI1@2j80ljYN04pmK(ia<)kZWEoGgh$4%F?@d|s za>o5BXD}&eOA=@-2_%X@4!$=93frfmsI9`{3(M)y%Mq9dY?2HpXEezoiYyNHia@fQ z>4&Q&)tl&!#A7h==ubRE;o;!9Jig1VWKY`8JpCmlA)p)HdsLNNcgfQDak(a47Qu=# zFSwFY5}XyW$y~wMoq}kS^8Z!JHc{@H7U9|lOc&BCo9@N9K~WTft} zZhe&{v{@ZN*rq>COQN@|zfaFNN*B4AesK)0SE$q6(gDLQ>I6q&e+6KSOgF0+-ASCU z5qFYkU+CasV$sb6UBmrr)M3xwL^g^zL+EZI<%%>lbmP#MhWH%%Hr;MoBGJd$i1#SS zy#71ZOJCQr37M`SaV$(0g|oUTv?v)uU^F=pVkJA}x&l}m_HAw(l_77s0M zQT9AruFHK&ikp%gqt>I2er%rLvE3oNo~C=FVw52*hJnKG-YK286ThOn#rcaiC8qDy z$U-o%qorSAgS$>P7q0iaWqpB^kz>D-4~pNU_%YUTMNr5wLC6k8m>_fqakeSWxI*}| z2DmPkxGPZ=gO$M<_71*NECzW{`A%`&6yFnnSG*Ijg|*R%tM9Yubab(X(bWAG~};lHZGb zOAcPXBFXPXz6Tz>d_|Jqi+m3_c=?JXzZdx$4_>|^$$yJ{o$8wV<#Z3a1EbFY(%+~x z(y1dHbr4z<4J1%A@!9` zFUBP6#=PH^G>!NEo;50 z{-N#Rp!Cr8$2aP-A%DV$B@#^8jiVtszim-G#m%lARbG=S$4+H9r5yOv;kjYlVC1L{ zXWYnDwi(6aMrSLJD$52pRN9KN&E8OH*ER2RK;J#tkj@swQ@+Jq;Oi+x_qslgJ0$0g z55LO=tZ)kAx}G@5z>~^8o)V_n(_wMypyyg`7@+9Ef(pt|llFEZVVC6Ul;~XbAyaSb z@e?bDCi3?@OB*rbp?7G{yZlhH-H{W-4UOud-V;~06(5N=>h2WxiJ*dC|=&YDt=FRs;%;W#t!P zONp--N*OC|ras6(y6oV^JXt%JC)DXQRZ`NX&Z|=YGfFTc_OrU;PX$5cGdyZ#n;qWT z_v9Cj-&X7gNar1;T?CG^IrS}z0;Yw6=6GN!1u`j+2cmhzq$o3vaCx}q@;ZoqincB2 zK$>x;bp}*YOgdM!H0F=egLpqtZ`4cHTro`p`l^LuPQfEA&sEzrppS`2_h?n(lBKFu zzr0DjYG<082Hr=gOpv(dlnr6gEaZ)IL-X;FaE|#sbWPd{E7RvWkRno)O_9L!MAA8U zV`NROY4sqaD9mw1kzt+aYdIfnDQIrIeY;`$cK+;oeGy9UfGNo28na|Y>`YU>)p==g zY_oU?$J~PCO?u~1XeIPRG6_}V?}tX&p=yR%TVCSz5mG`CX@??fM~5p^dh)G#+eND; z-lwSjkwx0U6JTD=PcMphy4O`@>O@Xg=8S);BSB~j35o`sZ(OcRWY^L?c*)RyN2kOah9(ZGP4Ql^8rE7j% ze$7|plHrphe!RBQs10JW!n}dEOOc&;$TGzi!wi^An!6Km z*F6}K1i2ag33uII3Ysjf`pP+}6V=hFY99>Zj*(iwstB*pYdP+DIckhFRY}c8wkU}g zki6fq2ji|AERqWzu;k&}BsJYvFKC#rAGOfv18B3@PTzrYMhRM?I+DR!3ef_Ueq3sg zuYN_aqRa~}r<4S@7O|-@1tXt=5W}n218EaZL5uLTA*)*k*^OoP6vJDgUM#! zJBieF5G9*um^8CEAzD$f=p*7y2IA`_t^>281^Ww}wVm>rrlQ%)ymqR`qYq)-NUj`x z#F~~|J1x8>Hs3#xQ=z!qP;$hm?q`qw=m7a2KT#~W&EY>G3229`qNyXDAw1`H@+L*tV^YIBr7%#G3$EoA0 z{p>wIQtV2&X~_>bO+}|?WK7~^5+{E;Z5_^Z2JUE3njI~Q%ZB{HXX;s~XGiS_obUMA z&$Y)jBhv=Gd9HzK*ML+>Gr!mb7{~KD95Oh`(%^+sajzUBbv_cPFB=)Cb`||_)y-1e z^a{S*$cmFh~5qq%AM zQ3_j;ZX`+enHQe^2-tm1g7na9YX+cFy2c~>@k zpKSP9w4PU$)b4xLFEZ-Hd~@HY8e`wRHkuPpO9U;?q(tlD-NPCYYW)N9gfphY_D4~+ z=E~qK7JrY;m- zQ@3(@KDCb3$LWN$U%kS%<0T=YlDDhrG;(niY?`Hrvf6NP#Zad@hD!O&ca|_cm%pm@ z9zCgpAxerp)LPq75GhMa*{pCt8)+nD>5H|fPbQApn{30d^wK7DS((!lYU*|Nc z0!sPPi+t6Xzf$`j)OF3*=lt|}9y=wJ`q*mfwbON}=hg?JMb;sBl0GDf>S)fd+GP6Yi%C%V|CtR`e^ z+Z1U+^*5_bX9W4f7y`-*gbA#YYZLdvYh9cni!GZpiwIA?{K!Cj%S>Lj*A9}`O)t8IwiL&wqD`fJ ziF9*vL-nIeKTemchZn6LUfsBQ_-sy_U((Fc$?}=Qt6R0c$gcAjj6kBvTB4nihzu0Ao&gOq$gB$@Q71$-K?*vM!sjk%OrO8Zk;at& z@Jbr?>c(u{_C^b|l_lN|9nWHmbjwq^xWBHixmq>HX>bZtg{!$7o(-Ra zLORQ=R?rBGU|P{-6D-)`Q=PX}KGg>A({EjJI#3Qp8R7AAF2#BP0)c3kl4z?igdRXx(7WKE|lSa(cDO!{0>jUZ)AmzPglczh!YYvUIiNi%o&s zr@)IVsuyhvl=dBWC52l}A+a@ubAJ+AI8PC^D0{p5+-~_e!IJ)_M^XnrI)z>dqQo(e z6%({ECQMPsq5A2J>L#w$FngsM4vNY^^!krM(G?rhfrt%pAVS-{y(yRTc~T(_c!wxF zC!me+l<=BkN%(PSSx!(&tt_(`r_t|eD|h^m#oNpV{@ROOiw zChZhda_K8j>f`hkD9NSIJc*~i%TmZS%Va}x>6KjrVdXX9fLEIWnX0~3iX`>8g2->Ooj~c ze4((LjVLt>6E79NQtq&EkSYjpk%2LHiBirG=B~F1Z%uk1;qHzOrl+2OT=M80CtLi zGj|pf+QXRupC!j`*I%nMs;D90YvATaeB4}7YD2g>LOw(xQxTHtr*V8yaN%4o(i2sA z&L}OVFt1WIh*SIaCdvGb2rPG9>Bb@*Uh|_Ev@}Qt^Ci<;;ywk^eFpULgiRx;h!Qk; zi3p;I6sC+I`UIUEXn8%oqAQrDC>*b%K@>;vR0)icv_bXLb!)nw?9@Ol(UG6+M-A_lAyT!P#I_z6n`w}#`KD@HI2-&IvUHY2R1Ub zd|e|m%C(IQ^hY&H$<|BOnC)SLn>AA7p!-jvdk|JnREoh8OrB~2;ssl23a1)?i zub7vav{;wfAS_eRXv$1MWF~m9nT62|!cRhbd5*qH7$cdAaASlDC{s{Ap1{g7b`T3V z8IiGeD8dLL%!EQ=wM~>POtEKS9)i&cUCVl*JT|S&wrUxGK`IsvSzOg@Jrj)OvoR$i zR#z-OsXJ>^zeQIWONtRctzaTol!~nTl`1tU*@ME<;$e5j;$bhC2(KxJDe{)J^noH< zU^c>};p#@B^ukC*8}l zS5>HUn+TeAHdLqdN2&)>eN_7-Pc%=Temh}OF%qm%LF3I@Mcr);cFCO{Bc@vTI8;9m z36ibqP$QGP>3+iY%(gb*E!9(&gOsqf2*7rLvz+ts8a8 zYPzMh9_hSSYAz+6_o+0!T6pA25Bj*G7G*?Ci7a^9*@5Ebn9$IyYNe9B^e4R<2w{(Y zik{kW&m7LE99un0?+yHCeU|D`fz(TmL6wnu5Y^CZ;Eic9$4>-m{%X`vPLz2ybn5gw z2~!PKBsyZmn4Z>x@H}Re#4lo*1=Y_rG{IH1hAfsUDa72zlt+x_RMHdyRMM9r zRFVjlbSjl}DwU)PsoPJqW0}jliC8NCm?+g!;qjR)c1yyO5zqFd?jquGzlVN8rRaw0 z$29SUy39uWA{RErsZ#Qrzon%aKOQOW`OXKw?K{qip_fmoc{%48-GNW9@?eQb3U``A zBTY?;J{Dle`PGnEF6-e!Kf+$3%UaE)<+8mZP=wA}qOM3pF%?FiDLlBLC4)#Q6;UkA zlz}Y5YaQDtSx&b}WAy|7-pggn_q|-!9$B!Ild|krDbU4)FuJH|>F#eze*-NMqsR|a zpGhh6NO4WeY)UB(*6*hyWzkS-+8mTvaOj7k!8bw{TrN9Z92dyBH;RQPBD%nGSyM_E z_xHJ6W-fazmpQm@x$Ir<^K#iZxv@Uh2aE=7_?!es1@VHuESae638;R$-j=TCdqLt@ z=LH1|+Ts|&>0PeHa@kKWn7drotCYhmm%a2xcS##sdKC212}9aT#7H_Ct}T{zDI62) zgr<(75>c7<1#MT)*{!A&9HTGA#|nPMVwn=l^u$cMC<{~2HmH6YY@4~(a5bYixQ#Lo zEtWM3uVD2pDDDDO9Ya^_yxQ8*8IA z`BjoUQk)c4H*!cn(p%_jA0vm@%o#bvkC8)VQ$4A;a^R%`Mv@3-@D)^b>vj>FU@1bu zQUbwJKy|U~cjt8Ai)Fnb^&EYb6okIYQ2lE9sxFqz)z`ik%fzaFc_Ub(qh`XlqtzXrOZHGDLlvz zxvPt13sp+pVwpJXwOAHxsgRtTREW6mqe56LlS%J?v1|dsC&UuLC!rR+XnhB5O=4AV zO~D%BcMy*7%3=S)&s{9*jOzYf8mwlla=r1t(_&fh^hBqy8(#V+!H_d3=DsBa#XU^n z#%nYKOo15Pgrvi_-WHVbpd=PuV6kjW;Y@`@_qAB2d-=IH?`yGaf!Z6Dh}7N~R6qOO z{ZWAGVp%uvo_bF)n$vrVW6>Yp8@*>VSjmN=8=)9PNf3Rv24%(%B}C z!tqG@MR5~9tN0knI8;AfZ%Nnl-9+)Ma}$FF18=aLyIA&xbLA%T3Oc2rDl9f92$Em8 z=mxp=n(la-v*Am2?H#Xv%_|gzOg(TyC#vissJRwYPWdQkJyro3Jh)j*ta%ljm6?8Z znKjmeN?fW?3L-PXgUyWeX%Yn=O`=p~T2)ZRPKk0~n3fQb+nRVHkS3jqy@R_z&50$9 zAi@YD%!EQ=wM~?}rqwmC9rPvh*DrFktBq4ESj1w|TJ1mMwO4SEmpUj;WNTs513RYA zGcr`}kLmvKT9!Jfc7s&1YDw9SsHWQ&DX9IR%ol<(Q4mGeps|obO5f~g=_;lOM3VWo zDKci!3Dplj^|88?hzgTPSQl$DTQv92`+u3Og{6(oAUqSL8Q ziIQw5lz)V6^p_@7DY{o(-a2I0BNee(u@sSO<*6U?mUTJ(AN)~gqmCRYqVF<$jU=WXdph;Qd3L?`9@#ZIIa7(}sHV`EgU z%GMVAu8H+^6{Z5#9w*hbornQvrF7NhR!Z`46SbF34E!#klM;B zQcm1BNys83W+-O?6IbIv@s&7Kspip;ST{nG_NS0vb`vd>T9^Urr?9C+=igJ~DOkll z>`mQ(bgMMdt@y~ib5WEK@lZmXPzQeX1ANPkMZUgs$#C zT(!?xKaWDuKelOEg0%^q#)<`4U_2VI9@C?NHZSPnUshx{JU}Rxbc?RiIGQACNjB53 z%N1I$=1+Gv{K|5Sx7tp8NK@$ywi2_LyIikvxZY0%N+igitm8T4Li1Wx|5QNM$wD(b ziPbp7o5nE+*{8LuklL@#{?x8)h`seA5`&i8C^n7R@Zvam+sP-S&YHV(@SJ}0(tg-hsf^;(j%Ba z6&^9-wZdL9X034d2Co%%8-6jtPC=Z&w@5r1bw5-;UH7Hy!U?nle(GB11O^KRkl6jirc=t09HNop$%5w%cB7#aiN);M6LmPt3w z76V1X@aV47Fa&ckD|%rSb;hNRK58e|>XXJ52ftb7q1D12qANI!5NSPNs?TaZL>aJ! zEhy6$QF!V}awBPEaW1*lK-A69P)BOoycuXn+KXnq4M{-r*tJT6$ z#YOLhJbKuzw0@w?DrPhnRxwkv$wc8-GSRtrlGYJ$X|peR-MBvuQX z6~a$U66rvo-MU@GCRmD4 zu#`Zs6i{6){M|VftA$qDysdRu{eJZ|ne;UU)vu3(4Dy9#a4_fAcp zg)wOC#C}x@oJL0EG@XLB$!6vn@t9T71Yc;nAy)O)6s!?`kZ^=o z4*M5=?rP!h-pmHoh~-YnuZv+Ocrwg%$xduH%xxtYat6iRdqPm2$E0xMSZ_>$7+q`= zV+iS!EzrgH1|_lR0;`4H3dd0wox57tE&DpV=e8n{Cy>ET5pYPK&DOeQIHk`fjoiCkm&gP%wd9J}H3WMWf<)_PPBCLT zMj>VHRIBI+cGo3FZ7PGE#KM7L|7@@;Ntm{{j16{9fl!iw390;G?N=HU#JO(GM$-BY zC211~zTtagtF75ECnH|j$Z?AgzDYje6r0J)xP(KXbcFSSxNOKj`9$|saVm~^PI25Y zcB<$POlbi8;Y&Ll7~;^-Md{Gc>axC-%~#c$IsC{BIzDxiq7to>|Gh-*<9tzts1+te zeHEhOw()wAp$su$8^d^-adc;FjOtmfQlf7xTkUSBvzy3RxCB8OUum)gGyT!|^#`Oh zRBLSA>LhB`<232Gr1Xa#RYdFYzhgde`c(%!pTK--$DHaW1uy)s&nH^$)%nD~_RpD5 zYzE5P?8Z*9rPn9eDfr(upLq2%{%<~k$^NgJPZXZHM$78#fR3>Zd@s%?PD@91{$HI> zoTi5Y_r};d{R?d@qKr$4Qr-xo{!S<(;*=m=_(4p(9+Ha3 zF!sNAQn7~C^9Pw!95$~)#!`hG{G_5IQpS{Mzf;tH#k=<=6?>Uf@W-V7Z)U;FeEnMHIxo;CdcXl60*{VDUGZDb?0`9-&$gLSGS*HY7_tq{g^ABzbxotVw11 zK;}|Ab(%BpTCOFAol2^gdMZdaX%5B15CD`NF-l;K%y#N$}~OP zit-+1_iB%~PJI3=|3CKL2Rg2+y7wPDaXk5}2?P+l&=)^ z3*(ivQ7CN%udA3xTE@S=(ovn&h}XWxP#*q$zWbbe=Z^kaBaP%4<<7FT?>YC}^JkxZ z&e?mPefE(!ijS%HN|kXtA-qfB6lu%L?QwJ4ADuH-_alE(IrEH`Dt3Tuj;fY~&yK2* zddZOCn_Z1Q;iDa{y!i}lwu{F#q#RptT+5wv8CPm4Zh`vUUHtJh`j+02emTP0%zxX! zHA{`*5u+reu3WL#Kb zp>pl_?$^*38x67=yMx%yxI!Id=|URy>Dgf@Sw7AhNkUVK2~d4*%1fwL6PJGU$a%qB z@%`z*D5EWPSzGEJJ4PnWr0p}>=L}6CpL`X-y}Y%%T^bB0c%dI@9?@e|(PjR8<|W{x z@#;qt_7Uw;9CWTQv*J;J=BrUbTI--Y^r}|5W!>%J&%7IRH=#zOM;{IABa{$=iNM9p zqLmp^Dt@$}phxATI4YxXy_NZfELf?r>nHDA=n}>lc|{xywV5Gl(4Ee~5ML>k9R;=V zw(E=DG4h~3rd|fsLe-PGB`xXc$Z^v{J0pkx`rFRZ(ozJ~0AZ&7=gXXZ!TxdZCD462 zWMBHpfU>Lsd{I$Kawr4A(Ki5#nMeGf*GbMT+{yLK7RqX2^N4R@zp55ml6mG49 zMspSs|B?K61)hiYzuT!ZrIupV_(_5y>Vym4?t`;bN9xR16D?X!1NGChbPoCj<`LsO zhZ>kb$hyXxVo6aM|AviGZx zB@NozErtYc;JDAqn?qQBfjNXeR_4k~tuH7{(?)^%X`gb4XEE}9!VKmRA`6*A3N5bFIi~ec<7w!GV)!XjrPeR}f;esF>8JNg~9vrCzt(JA*=)1xjG%`i$!{GR-=mEOHft)oYjLPywK{Qmhcv~dQe6+ zlcaJCroBc?k{XzKn*-b&N{?#p6w2IIwlgH5NG+rm%^IzlR_i*^TGoXHTjm4da@f*1 z;Zg~fVf8R>pTPeK(jf|mYoS>^lq|*{lbUPw;MkpjPr;zlvP)V2*yDVw2P!YwK}n41 zEaq@7Ln&4P1vko z1Us?BH^`bQ2%mU^>JKrcfcoh<>7VEFiOMrH#_(CN4qKMBX0(M@i%&f6l@6HwP@0-! z)xTs2aT^LCuCn7}j!ViN#_pWySY{THNE3D5%7G?&@%MPZc<-M`=s9;Tef}DVgmiWE4NS5QB95QE=@S| z1o~k=w(V_ko%@CakRiH?Rq@_(WQKzKk54=QwlIG)p{MICVI+9H$LqEZtB8OeAUU({Ms8re!;vdHZXW!^3S}V*5gcUt*`a55u9~w zt=|GifMaWYxWaG;bs=tku_M5-wZ5^{%&!aWh}musn;n9S?TG1J<>rTC&e#N$3W$=< zq6|c5VVe)AAQg0m-UM74wXoI@`$2Qc&o*1y zRr$0&z`C}VCwvA`RXeAxIG(z6+hfO&bt|+>gZ#GO)#&+r#j%8+x$c&l9CM~B^zXO zSCEek(&Ro9q3tE^MZFq0OIS=~m*U~G`hfbuTY3L?V-_bW&neBRGDjDzM}iVuiOMA9 zGfDeQ0+Y{i!V#0tQ3Xj}P;x3>7NqRcKv9F{sA+qL;hJDXHoCAUyOwf*r{=~E@Sts* zDq;tCqJGv4+vH~G7A(Ah9Nw|q%Ywh+UcU--Q{sn||*nI6wT0+FMy1zBr z1c^4E#wDm%w+$f{Z=e=zKwx0J#u}H^7#Q1t*G9c+j~$X);kglQ9f}i35T&1NAKm6n zlkGs7u_5DMXck)~_0`JuGB0dkyac|cJ-*6**P)89y2WE<6}1($tZCV4;%S*m&}^-S zYDlQz7ucn(!@zh-xr0uF&69% zCf;b|jO>`HS}NaoPpnR{$)&<5(Bx7EZU}V>&JPaK7Ftz3Z;C#})C#69*@yPzNN|wQ z6dGQs!dYOiiuZ!g9PF4-uZm%{)B>R&EP$PT)uQScR+m;+_?hk{m#*I6=WK#2 z_v%=n)7qGhfz#e7UeGJJb9wZFvXsUoj~-?~N|Zc$m;v-K{n7)VRIop~u;ih~CrFFC z=d9Yh_WP^1ynEUFeT@zw;t%b0E8k!JcYlDeG5q}xzqW?Izn-8}Bzfjm><$0VYg}+` z`TGYp#r%C!?5Hlm-v^xX8uRzBT*U#xCHU1@F$Ar^-@g)@e=(%1=kLpQW=T_LsKwke z^F__6sG?DiFX^nxm~imbApYKOe^vRl)ie2DNLet7h##XHmL*`B~-7PphJ;ieCYoW_9SOq^hWc2Ug|KufXoE z+!OZAwy*Szd^vXnKd=Npr#3ikisg))-AyzK2^Apps>*6y35bZJs;X)Vs#~%OxHJf1 ze5V9{-~R31O_Is5c({|dKWF=a-5)waL{k>JoLCI#=LH^ zuk9TujVdKyn8?Q$LAY!=%qzV>G^G#QTek~O=?X5h^oYEw+Ss)_B3(Aqb!bt?9p!^9 zFM$w3^*+i5N$SaYpEl*SG3~@cr7{wy4d+xVxy&$FI|l^Y%H07HU=7nGjw9CX}vP_?K-KC4nxU$ZLu@6()Tg8=$RcVaGMRFjbYt0$Pt z7Jp6yXu0GsjgBrn6MS05LxuPHoS$o)%emXwB;KtdX}#%xdu?o#t`O3A;mc!tJte zYS-Ity-kS^=&tmCOg4pk^gzvdKknU7$9mU36z zQvQmQw5Rf~;~)ma!g$y=&OV3;><%0lwF{4F&1D_x@}$i;eiqUTI-eOV*lRrQopJ_yj#WRX z!{n~~hLrQ5X0-w3H<-Ig6V8EiS9w$FwuAq!luU;Mp|J1+y~RuM=n@vkn1){g4y?b2fp)$3s|`qCa|t1Uf;_Gd1W59_UW)6V4G5LN2#;(Zye5xy_OJ;JQ6cSGD= zcNgE0>Gq3-1Z6E#5~BpQVhU=x>K*xQcP%(AP`X}P)B((m9JBPjz;=swu8$>-X-eGA z5ZwyQ4xh|i1{@yCY!Nt|xfDQ;*;>c~GfvPs!hO%(>|+y?Cnk5~xTSbBOA9MMsy6{@ z__lkqHUAdCCZVK*2mL_Fje4xErJK*1|KWKF_$XEVsNFuI0L2~R+Ccy{O#(z*2|?C; z2c7RR5!<+m;{610=AW;2iCp)2|HB7PZHo9F-tK)5yClx{@PQJO5=tSpnF<&rd{kM3 z3)^1+7ga3n>~*-X6jaJGqcW4evJ--QX|`j0sT>@B+w4nkyc@x0PHKB{$O6IO9e@Sw zfOU=h%sy`?Ft_k7WkF>O&a(ryPj3y*VJqo8J760m)X8Nu-!GBnAMs1{Ti1Cz0bXDj zaFC!VRyz;`|C0~)DX4qX^f_H#0P3gbA^#jWkaIOS?4AFWaqgCu!qu|q;Eac|s&ycD z$C*p5&jMt)y4yX>Ol8;ttJG6vo*7zt4iFTz2Ua}qQ_9|ZB(kIdD&Uk&jmW68q=5!! z%w>sY$Ce<`u6^h3%oHxlA_N*b3)D|Wk*9f9$$+PN} zC0}8ERB6F_P@gWv^KIJdb28KC=hmD-JevrTXE!jYSC_qGasX;#0EAY-E~*UxEuOR_ zA+zmnVb&|XCDW9DCKLAsaZ|qXdanlQ$9rqj%#WMZfY(2VZ2gv*ZX4u|G4QTMw#(SlGxw9qiKr(ro=VL1hO^LH^%fj3puk^hn? zoAxM^4T64ElvT}xYf!ehPFYz+FVMBDLDOmCL6b_5uDA{~y-J_Z)deaRwx?=8;HD*O zd+nvnnpCUbiGINUlx5~2;S*LLNYoc%VUT8F-n)7_5^qeezm2Z<mecXfpuvNg z0fS?iyx;=fiu)DMj4fDuHk)kCGe29lysP~BozBEyxzlQ&YDpjFf%|#$D5F!X<*8yL&;rOH#$qCkbGEm9Z#pw?XaYil$QfkdXL$eQj)S5jOj#o}{t+-YM~SJiR)$n6~(^gA_nh zeU!n_DRD`$v?@;lZ7FnigvBCF3U*=upmtgPMv|7L~aLL5|Xlqj%VIs@MPvc0k?yu`wbq> zJOI=jI$-daI~83#W~Zck8NvmNcXzc&${e-u@7o11y(8~VcK43FBiYqEvOn3;JF+jC z&W+rc~jYOR$?4!y+eXpoP5G!D~J{j!n&&(-I{Swd&g-xj>7d7n*Qxt_MSZ`rQ zhT~+XN>2zvJ+lV=CO>4VFqJyzU-zjTC%4;xB${AFZ^@(#q%sOpnbV4eT&D$j>maO# z!Vf)OV3zxdp8I-!vgd6*Z$}|sAX@h3=WNOfG*$lKJi@!x5Ost2JY_qnsT zujz4~#6B+pRT!#%RIraINO9myVQ@AGP^AY1X`F)$Qj7Nt3GYWKa^@voVs{u=P-4Gw zf!l(-#GbbzSTKB%1+@a4%qooBUB%7Aw>gR3QUn->R?;>raOI64Uut;hOXc9O`jhr$ zu-On$nLh_c4wHNV20sI+k=WgS!3Sn~yv#kf#7-H=urc*5agJFt55AxCJdT zuUZxzjAmt3JFkA^%WhsRkW1L|qRHy`abPi0spiuG;?Q$|ortVHtaym0pzNKjK4?h; zRG6%u7lUNkIR*)n4ExSAKKfXhD>GrCPhpyv2kIv_Imff4;WR6vmY@!$<#M2I!A|sS zith{-VCHaJIhoT9!hs2r=O8dio@(HLz4ICpl}i}X*#)3JE?)IgI@QaTVDiW+0YBAC z^$E2H9ZV%k3%-N;jQej#fzo(R*;Irk(@#kzeURrQ;hG6nGD&)3s+(70W+O zO|>Zjoz#lufxW+dkr&RpRV+PbkZ^}p1A?@8xFY}F!n}9Ya|GR(p4K4u^s81#P7;>G zdE6>cL&PaX(J5)5e&Qr2jfO&Le4$Q>+!~}D=1Jq0Em^hmk`7Vsi*yLmxJWvb#&B#aF1J=2DPB$o;f6-|1x)@50^+xoj`tQ|;s9?*2BGp0dyE zwBQ1A__X4Y`2=g_aO6WXOTc##?VO6Ao>j=-2o|5gU_Nx!3=LL_(W^H*Sc%#KSFr70{xa| zdnQYEN`Mf)3nBapw)1xJ{DHGSuQ?~HfdH=oyKCuTf(7Xfg~Ju{C5jof7%zb*CpTFx zud-={q*X3%%;#m|_qb(i;x~m-_y?%4*mQjta?5iB@3-6gDv#Rl!w%AP+UlbWhJts8 zVrk4YP=dD@XK_94f}Q9(3^I%bVGy4Iib%>p{q%g=KhI?lm1l%Ov@BS7Ez4R4@x4!o zL1YI+IjUduCCzo!NA@1MZpymI;-k{7A%;4P@`iuv+#o7jjmp_~PzexuMZw|vp~IIVQGy?+%97^{lU~hvjqs~)f}!ld898-VkS{lghV;_bl~wJWc-A*KPA<9 zO6u2Xq2{y_@JBC-`*=Y%yx5+ZRl0LZSJ7wXT=6zuUPtt8{J(BW`I)_fSu3)bDa`u_A^WT+jVe2^s8>BQfdW^ zdx01YbIXJj%Czz(_6E_Y2nEupNT0IQ*S<dtZer_HwXRyV>=H9q+a@t+YHhS_%1s?%2-JP)smfJmi4*9f94^n^ zy>xa~xUbBd?Ed8c$STO!n|2DDGkv}c1;WN0I9Ij#DT{*m%5`MP_Qy4KXdOuND3XVSo3#e-)$3?4qy1?+`*Pb))+)Fv}bUZyO3EK@S* z4u{C}WzxdIToq{F0&QI2xzeT6961S+wCIiUoff@u!A_?ylNdfKB+^iouk;0<{)A7j zdMN#%;z^%ZdU_%SwnP&O_oDV4P&`oSr2ZW;$at`L>Sd668l>L#o^}{?^%NeG$`?E& z)EnU8oZbUx4Z?I9blO`D{?mkIKrn5P4GV*A1s1KAEzI$@-8rG5L{Ml@^0sStcDG&I z)1hQ4mNaRlQ4va`qDg}T)lKK8;gYam3udJ`gS{-|fkcBc{Q89(oZ*+FEob}XpUFcs z7{uv$<~@-4QWL_Q(f{xb^QM!Cy|Pv|%?(FY3YZx;xYqjF=Xb>VS@4aPRg@|%YZBZsvrl0-CXT&lwJQ!p!N>c`Qu#^|Rt_WBn`>me@NM4uq{0XnJG)Y;dX2mYG;TJMwxhgDI#f*3Xtu7;Qd# zmzcxnsDAdRzc1F$8b9uIud#j>0jN(ba+B$2zw(E%e%2&Krz>!m!4E5j4wLpk9K6#K ztfzh!|CUzv9vzq4hn^OVK+~A-R`a>BHlJH zv^Bv9%G*{8*h>5v=wi;qn8%K)exaMV&nNj)nJj+iyuQlmgRGKdnOg|Cxn-|n`ia@A zm@d%Xt$g5A&pVg0m&Fyxapu;oCu8R2r^PV^~;pJ+s>^`lYl z3*x3|q<(r|^3S}V)?;WZs3ZM8+p4Xgb7?!rR*9Z|PerQTGP)Za1}@gy;;P7v~a zebk{@>zL5mB~8!{4hb$|E2wO0tX-5J8}VwJh*Qm#2B{s$s$tRWKq#p0*6ScO(L#ix zg#@C7fY4TO^*IH)s2V@Ofvuoz(S}oo)w%{>Qb`M6WuSgle1*1xwfI`xR!~_*t-CC1 zaCDk@aHJBnOH&7q0$V}j)}k(eKTS=wFwo?MZ3XQM)+vAS2R$}r#k=#lnnp?rLmVdz z(l`FStDYn1#`N?qu&(`JW3K5Bb6FDXt1U>x>9*^<=+FX<5FzDS4oJ2d9m3SNks3d)j-j{;rAgV9$vH7TnoHCfhFewcVFuM(E7 ze66kE>WA5|1~D`TX_?s_XZCEgn}G%^iUr1;;TZKC@)aB~c+eSx5%0ogZRjH*(t>?_ z+?km}oP$0yGcjMlR#00SV$lU_Z3Xk9uYOHUdRO_oD`U67Mkf^zrRksyWD3@LuhFld zXHNYtH9V*MvMPa>1FjZ_$~U^M!X4Pl87r&o5;n7Ya}+v6;c$^$pZin~Je}(uxfK4r zH_E?@7fR=fp#z%j;^f8LLmuto@Qqu_iaYNT0y=W<`U(v9v$;)Q&AqGCk=yiV{=Cea zaV2)Zt{*)})4*1spO8DBBi_o449uR)_&WmKj$tnbCS))Aig?hn;}o}i`^%vT+27e? zqH;e%gXYL?i4at1EK^WfWW|)DKRhw;__KT~GLkOaifqtZk>yoTK8F>Vj?F?M;Sq(1 z71^K?F~+D(Tc-pYf?0i&W31aZS+yTIr;nsL=6zM(-zB>)_m{kH>$2q+=8=H^+I%)5 zgzR0p{BnIm<(Dg`Yvh-w|EflQ`3oQHW8kfw{PMfUV)=#9*b?$fAPH!74y6&M<}SkF z)VzB6<<95$JtlWDKQ@Lx{LZ6=%@?yyN zdDkO~t@>(B6Re8RJKM30O>!v&+-KESt1_z511U#jfrjaUG=)knB#BLGB18|Vhr|I_ z3_8PKij=OO(i(4;|c zLyCcp6uC~S+56pp#mYXg9dB*@{xAP&4S#$e!H9nU4L-QG{PBnW^P>E*`2y$=?Vc+0 z><^uQ`!2=B`h94UpVY3CAVm8PS(}$2bHa9}U9b~`DZUc~0`2sV4TIxm*Q()?Qy5k# z3)E_(Nut{i6*0ds!>tRwaP2GQ@Y$9>m3zO`}q;VU1l z;fJ68Tn#_`7e2VQ{P6pJHRgvImMx(#j``u39~Ns`w}rwq981(fL1A$)D_0fA{4mQl zzu4X-=7+aExe_h$KUZRYIOc~XJ;8j8GnGiod(NgUfxux#;LAyd4SPoq-GxR+8U zfNVBTDu{Z10jvDEM&Pmigg?45tgQ^Q>1-iO@W1}sm^0p(obg4lpLo&NHx7UN_1~)D zk53Ye@W;RDgKNtl?--2v3bl~a!+{kU5 z_mp!M8j#Xa&-PrAC}dJjdG;!bD-CrlvCp4t?9xHSw9Y7I#bwkybf=%(VKH67tB&9y zZ4YFYR%RUW+?Po^3+fmAl_UVUhB@$f|6}*xe{;kiv$NZ-=X~ggj*15_`}3jiB;>Cc zxW^t7m0#twRg;|$eLc9T_k1X=uy3-9pAYSM>%ANibKu90pW5i6^F@5hcE6U(* z2h=x~p})J6EsvC`CU(Z8QDSdG_gu&%$_RY0=bUP!uio^Ly_g8e8yZ>9{ zS;OV`CJsE`L!IB+oh!b-BUk)I&PeE#InOG4F24P_6oJxzqUUXo-QRY@cJhz?rL~B^G?AkQO~1w% zR3w#vs3a%YBL33UhS*=4yGiAcyUk?SU%*69j;n_`iQyoQQWSpZ@c}xRt6dHfQaJX{ z=nxdFA0W5tCMv&?YC3-UM?~&*`dhii zf0JFDpJu++%+m{V+7XfWU|6%7ZF1sC`*N_>ABIpy|%#;t+Xn_4DVElr*PvJQL# z@SMA!{Mj!EH&yDG5RPCO)3)2r{b7QbnRFP8-cSA~_NAjI<>0<-wfk>wvH2&w|E68D zPA9wPG#8(v-#+YuB?Z^+{+m5+Ppx3|iF_+u$M=)R_mj&RSLpMq>T@*jl^^l_DWyI7+M`*GqgSFiOsy83P$7BA&U%RGj*#Qo$y{fOI1gZ9i(s(0K1 z`HjyfyOL0h=3p9VeetBZ!cc70Q_mIR>+s7<4P!bsl{gOl}MG zrMh^5IZp$+i#$aTdQB#p3QZ$u5RD*^A~pCdS{w(%yAMco-WU+>g0X0Gl6f$`pIplF zBxEjyx-{eKcR%@8?h=EDKX>R(lcMQ!>15OI7UTQLjdo89IV@deL(+QOPrlCj)$byG zOA%n(SGI(&`u-c~i*@mL)+-qHA z6Rk7w3}(>pOPwlTDzycA10MlND}i^2Us764ut5EKi#yWV`%2rG+uD}emC}&YMxR79 zIWSg{d4tEpE5_Ys$6>$OQS}&m;d^18qR>@rkC^I(#_b244APwHg;rVh@h$99l>8n6 z_0txjT4(k&cXhONxK?Qau~uHYxC?t_Rlm9tr#2L;sbmVsx*%L@0XSjMT}{mb@EHZ! zD4GPy6RB;pzGiZh-I#Zi`W;S9H=>NWq)+rfn|$(wHOy&$L|czllWK`|d;C=;yqG-w zgp*PCXed*$x~D>Ms-0s7gZ*H?t;+jh|GppysvUIx_JXR7L=;k1B{nk;lAbSu@mr6+ za>s*FePt&hudlqu9ut*s-ll$SD*w71#sH*<{KTo`|`{Yj<(mNugO$$$CxRkAOxC{ea|i|?6?FWNrlQkDTvS{8CXAdug?o&^qN+=5-{6Rm$jwt zF_BrqqC{pVYr5<1lwC>LklhI&Sv&%;4e$vb^ds(f^;q46+I;q1DtBH232@bqj@n1a zz>33oP2piS4*-(b3xc%PK`aq0-ut-by~MqlH(c!!848O%DKcaL26wQaQLUPuiE>z#^yyWy`CUusRI{gMS6M(AArB3)gVO&1-xdUNvK8phlbg+dr%;y)7P_@U{ysd@A$ey20fHi~ zx#cTB{Xednx~n%w6&0eQTjDCpGsdDN#TZ-akVy-Og(IatR@If6urR1FO)LQQliq)T zXLEr~^P~_KdFblRe!+Yc~>wZeQm3Ditc(PjfPQ`sOc;RMNZ z8W_}T%HBatKUwC8@o-Wm$SoVUdj-;YV~@5?o{>N7>YDSy5%#NCddwhwkh5w)kQR?F*T1(g z?_KqRZNf(Mbg&I>dVTuCU%fdFwihAdF4c)n$pZD09>5VXo$?k_CNI<}y(2%unV8pW z6InUTyL!{IB`aA3XyZ_&EZiaNB#WdMzIt<<8ZA=sl(MAaX`p_qpeAJ%r6$Xo%C{3w zW*%xFG?4Gl+@AvJgfD7A3 zx{rkDQd`uTV;mIgyxvQvU(T)jDJpTO4&(jf|mi=IcYJU5L>(X?3I(7 zC|su!&UUE{jvoqmId(xTI~ZagHIhqhEBP;|ls+UR#@$GcAeWb3hGQ3Dkq5yr_R7U?n@#ST$i-Y zv=_N#?))iE$t6LUVTwvk@P>aO@`&oMM&<207j@{c?K%$AH;M&S1$(=+NOSgQ<`gCj z1U*GzIr8vPeZDY7QJ8}D7G|Aq8)(ppBr5e;#bDrc-i4{utbeWMQz40xN_`qgu&f}N zC`e@#q%zZrrR7tCymio@OyuYwlM{z+`K|7}?Xz!->KDF7$m?f5Z;y$}Ctsiyq^b7X z-fSqTZ@=wD{+sOL`)#p4I8y*i8*#tmp{Ezn2gm)rVf*`Ai|PM8BVtQFte(x^Na;3z zqcC zjVfhuX{D7VnWwa}R?`}n(LAMLZ>QN$`Q$gbTuO?)dDL~Mz$a!I}slW@+`QjUE~xj+a=cR*4f z7q5CLo$6Hrs$SDdz)$s3eN34~{$eRrzVIE?r%UmCo3?tK^s18hJ~^+X4#h6vM|d-Q zEuyrVB~6Xkm0(VR%WKorLO-PpuEvYmt+0NL$?3vMt8mJ&S|2xaXFr6dMJy0n85j^+ zR&n(^uToY~HG^f1RC$_s*k_d>@v&W%U`Pms0Y-_Q62%iO$|qWcKhp@M)s%{*#|)DH z39CjRC;EeUyf0ZiGlNynY3&=+KZ_`>mgtmm)rC%(1iDTc^F$mftOKSsERDfvl5okk-a`wd@stKtG{zk zR9Wy8A+IbrWsix<$cM#nVAU!IOmJ0IV|pEmGoyE2*K#~hHvCSd-^rZ5@52?^| zTd$*dH0T^x*#b{kKXILc;yNn^U0Tl%loZ7rFb&i%u$2%QM{Ffh3c_m|{cFk~Y|qr7 z->UEQabnnW3NtnY!?AJUa|XXdY7rn9RIgKA%ZNS7J~HyvwNXO(S>?@7TXboaH*$un z-xoM55NO$Vv*6e_;bB2#^*Obn{sRMW6Dpf-$%aZk^)&00@aUD_Z};Yc8i@{|E5@y6rcu2C`!&KZ;j-fAmtG*6(u37O zK}uI}nWeAlyuibEZKbkhhIJa5^g%*?N_v|;a$carO-bFN2$}$rj}4u_oP`Fgnsw1y=w}|-zj5?4U*9R(ciI$)|0F@pta)Z4g1_m5Hs`IG ze&*q87uC;PqPlfZ8T2zeqh8Q66NpW{v3>>`=NIc|{P{*EVRy1j3t< zB-L0y<9Le!t>&;3-l!;Gg%%*>U>u0Xn7wc!3`)iND zBZtf~th9oZ1&w_U#8m>4tCV0FMn#h6#Iz&@FOGgDI6u8k{QNOwj7IbG)(Q2Tw|e%r zs66*ILV-PsJ#yaaB zF0~K!mMN{Im_lELOzgd4q-+$LtZs{|Jt+Z2QHp(p!Jm;?;J3hA^f7h|7Jtr71E;VGt#$)ZuFFEe zSPjVy;q387O$6E^)KAY!tzY;BaZ`+3{q(-%pLsv6$C>#4IL!}@u)K)xkBje*(_*`@ z(Zp5;TU1M8vOY}41oAD5twjE%?GD0|KJop^-i&9M3Cwmw+)fD!LCt<*%eU9qW1{lbWYfvFI^R~Wd@IgF z-(;7RZzX?8<Na)d&=A~W zAChiMIe&)EnD`6eG*LMwcLt5!ubi;@tBz(zL*`U(AB{f3d~fgk#(IY&;x27LBY#@M zEU7;gj26o_+i8?F9WG9o(GM<8)A~(yg(4MGn`^NwAis<5el8|4V|BF2)Do_5yB~FFcB=(KL zD$9(m$vR?5l-ERVYfV0P;NsXfK7~N^oLF!F-ZzvLwvp>?)7}Sqr^#Rq=Py>|FZe?* zj}F;)67oa#9(znwe(@De$4|dR%Tt~EMaww99O7@C`01y9;5jnB53JevZc+Yfb_4KN zYD3Ilt%|>D*88eI!sY5-OVHr;xJAe7&;}T`~x=ih#Q zD4+kGJ#s$p`y*#i{WnYRcWL~u=Y3!tyRt5Ed$8Ikt58pGWmr;`*=iCY z>lfb#7T*UZ_Rw!eLM)?Dg*G0K3gRxyv5)=)E%60=zoz$rZB0ezkD0b`{`gCKOjO>~ zeDeqAV(Xqif;$tInm_8^&-l(YQRF>E$SX%r*(3fMzSvy*8Ejk|=IYzesONmai)v8v z?W;qS{GB}}D)%F`G?~B4wiVFRGVez-P5xS*w|@5ajbq<^_6iNB(7yXi1S9s{pZ3AE zweP<1uPFyWj<&PvMESnfyuKK{cR0)zjGWaskDcvA>ZgxXrhZh|ik=tx6Cc z)KuX<1w_(KD@dUV`u&*T##y!%`A!MgN!)c?qV3?r)}+Asrt?&BXisL>@R;?j+SXFt)tIb7*VS5V`UoF#$$LE|$iIas!7c#+im3Iul={lf=iWQAJKa07v%PnO`>aMD zOy)+m4e##Y3{ZtMkNHzUIwSNxJ@(7$Q0PXROzMSMpni%g zdpP%<9Ki)y**j`^nzX{Lz7cMfkLa)>2ofwE1IN;V1WO09+bC>(M3li7qH=k(+zyE&{~Etn*QetOo|`l$v-4OV@NSWeVH>D3~oRdBqWrShdBekf zPWv)MUi)Tg{-Q<4|By|)tv&wLhIst1X8gzeR;B@a|OfCMz>o{6q^UnAt#yU*Jr z>)j#IWmD;Y)7JgHnY?tlYy~doey*4LZy1V;-Om-TcbNiMdM>SZZoI71cz@cf!3VX; zh*!S`+$XuN^?!H!8qHVhg#YWCVZMFjspx$BF+zcSVULN*3)4+E-)?7Cs8_y7`EQn( zZ^Qkzde*Do{dyFCBZNHue%2lnl~2B|>G1c472xmkW#Ml^shZCI=OY}55@XYN`=524 z@1AP#d>8W5&4U6eS0B$%VzbzbjjNI9}eg9bz0BA#2V9J`KdYQQ@bE!P3F@U z-H$qL%U)~mN2v|DA0Pg5lz)7Xkmn!YW{-)=A8}h+lj%F| z6uH-_-w)2DFTsDu{79w%W;Y@~vXp+m1^vZOY5<`3b#_S3Bv;13Qp~SHL7$f{5JmK| zbn`M1cDng9m|icSn-8wD-reQZ-rK&?GxFu!k&|X2{5iEDp6^#E`>FiQMa)@A0x@-z+iTgTEH#_u4hiTYUVh4e|J2&G?V`l}v$A zz7hGA#q5uoQy%~97EMQMq2FxI_CDXH-?eD3li$`!U-We2?-y&)^)%+EWBmzJShMP< zU&H3t+RU#lI$qC-xo>g)S8a&--_`KHq5Qi}>*e+MkZJJ#O8xrU9VesmdnX|;zh7gI z=xblubn^QfHC*eI->>!GEFr(g@=vA!mNugNvzWei1>@y&HRFX)Fkb8tf9`lPZnFK2 z;4DJE4tq{)>1>QGFH3lEzaMl4KaVS8vZ6;-!cV=Xd|v~+ZM5p zYSH;>nr>?K`AThw=d0DsS53=be0I(Bwa)NrKbz)94rY>8jNH}{DBF;*a}DxrVh3}I zn`NlPCD-pws0?WJO5jcJ>(<5RkL0Y3f2pIWIyGPn{K_m#?SrrtQX7h#}?1G8+N{3Y(Hf} zl+<+RWjBa@)!}5r=jT`+hG0X7LN@=c*Lj60N{;n|8&W^`@-;jEdlaJSMd!zTB>OVx z#`fd?is?2hwk@Y%pLB)eq>^T^UvfQnGbyP%iDTMAOCLCUI_#Y~xsK4)8wI|Wc22yf zsqdtLd;;|x;Y6g3+T519E7f*wj{k|=5!8~8GD9UQ$A4@S>C83PcBuvT zNngqR6kOwg+R@*tAdgjQ&0{tCxd|1~bnC|@x;Jo5?2%h^{uODo`u>dC5YN9Ga{hfR z9gCS)2J6pa{(x2b;>c$)e*iTj z1{COlLrzK|k^teT4$Mku2i z!-z5w7)JaR0K*6Y3?uHqFyaoxE_Di>r1hXh=a&KvYIXTTZHVQMRn9MNB3P&O{XWm8 zpu}jbf7yEWmp(KS-CueqA-}(Lk3Dj~_RVhk>DFY5ipYEGc5|$zSDpqny$wRxsjw#O0quw50Z3EUKYD3Il zu9ClOQTi>}veDY~t2V^+``o18!UmvUwIQb8RnhN(r;%3oBg7s#=oOFKv3DRK+&ZNn z0wuN$ujdF9g6h@(_b)|-Dz3O67k9pj4J> zlh8L^=nq}!A6)3ay3qf0q0hR|-@DLp7yAEP=)V!VpX#ZM`)R@-5mt3oR&c6~W_*?4 zCN*t}2mOe%Yt*VI-Zr0o*MbT7XtMgz2|Y@)t2nB!ur%ZVHF;c+;vIy5EuOHx;=M$j znMbd7iCp*eq;kcb$=rccn{u~KwH+%>Z2E;QGuvfV9`;~1mHTozcc6r3hU?s{2oDuj z)|`{;lKs(<$m?w# zTY3qKNK-EODj!TJ_|b!zJAfjCE}(vT?(omllQ}1-fy>d%qH^iB4))%aMaN;6v$K!d zRvP)U+{m+)2Y&dH8f3V-@8kgI84hqltwP4BGS8gPj3TJmL!)-mIC@XGtu z4N$?%wqF}5$6a>Z+*eQoG~Vq~w<97PDGT+1vJ@^x6{ex%K>f@RjOSj8%d1tX!$u@+ zsjZNpg zKd3Ny6qJCU>XqkN^_o>0u%0!D_i91vQ#J@1fvQ)+=0rAjOnt$|jv#qffI+>=_U>Tj zG*FWTV3S92$whquU{0}>B&5eTDx)dI@YAzW>qjQ;3*v^Th<i;Qhp3`wNSmKMyp0b1p7`FjRIOhb-G>$sfiXk6fIN`Ei}yR zQ6DuVrzUsZ#M6dy&)Ng<4d5dph|TPtrC+BM4sBJ=e$=7)SvhGt2>7zVQKdHhUS<^>v zS+tMnKej7guccBsJWA;`{bSjZRX;E3ALYJC{}fIdkzX1hzKH7P5W%Jp)8q=R2_KZn^c8RmKrKn`p(B?n1y*~Xq~dX6 zAedzP&)swI7x0_x{B@Erwuw+3$cxzNO_jA|xU zUsHM=hqKQ>F*F39CH1Cp$=39})QGH}jC zOnxD7)Y2fwkew1>WbnXkiG5dNXz&2vXtp`~peCYzC*+XbqpsEG!V>T|9#LW(e_AeB*&%FJ4pf#pyDy+P7Lk4qKs6F4}D`ZqB?0N%etIso;v zvbH5Z_g=aN0aQMDH|bTspMs!UR|!v;D}buU<7oBedn=HAp) zUa}mii_K#?v>9I{uUUhsW1pV3iv{GOaB)1EeKjf9%)2a6W#aWiRR4ycX6lMTmp0H3 zD2n9MO2aQujcUq|s7AFCw80YCoKNyRVYfQ8_I{n(5bt-uR@3#Y;aZ8sPyoaZj0-|H z4lZgh?UL)zrtNz`zo_!|!I43~la`e~!O05F#)x(?aw6iaXqu$XN1CzkmKs)85HeGJ z^n-{$Bq#-uy@MgtEGYA6xJ?-VR;{BQn_2m(E;T}89K*-P%OD&+A>T}wrN&(z2>@|w zdnSK~K{agV+ja)j{%DKip~uxmwllJ_KNh!CzkRsLkd&d3+pk7jLl6knkf2t=dcnfy zRP8Jt^0vf=;bkxa|G9)=cnv?tO_$mj8VggVOSA@su@R+wY`^U$5t>cw{b&&^=@UIkrSunV4)4}!eeqV0Rg)SSdOX#-om^ewsgUc#_>$S(w(&UplVSE0#>E_Bj=4=2J19_uTt+f zcKohu{qI92ZgBl4tOJ{ke|dS@HMH^ki|em?^eub)F9Ydi6k}<&pl>DQ^)26LkBQ3b zUZL5isq`(uT@b8D%Raw>qMD%}o`PHG{097=27dF)-<>PAe;)SAbVnz_1bEU-cw#K< zeIOQ8)!Q~vGBJ(|i?G>l0`*!(wyjPO@9Z-kJ}Ao$^tDQffZ?W6zgqF7mL_=qI{*vV zXGj#7x!W6AxGa1n_|#T6vNkx+KEpnJp8wR6%(Kt1R#PXJ(LDPM7ag(B`12pt*l@~N z2FLFcl%y<%fZ!1y>{Ae3`mX`C&=i3B=~>!j{Q_OOtAWcg9CZiX65ip|_uhX^V4v~C zi=BN&fDBj3s(nU>O7->`X*~xBir8ngD;`cJ1@tRq!jcB4=oS|SlXW~}*} z*ljJa>7_7vYEL1_H$wh7OY65RR4!p@J^)5wvwBaxluq?3DNG*IO2AL`QhijfEct3% zMCEH+WL%K?bSa*1(<+~O_1iFjwerIyCrF-IU{J4adj|%A)aTWJz%nD{qS^q)E6AOxzd5P5H{}y&B}-TbpKXHOmYMyy5BJEi*qvDltO?QL~J{;XBRU z#y``gj&o6Os!Hl_wZfCctQt>w311%}P6&(GWC%aPo8fB_n+#=BvvsKaVENQG^^=+_ z;t~pA5G@)OOhI+GUI(d(79tcaBoHkG%-p)AQ8_(h+n52;ys^Tcf;6oZQ2mnln)diA z1NE!oD>TTc#nq<7VG1l^dP-UTsu`nCSBBl+GWX<;HBSKT1u zNuYk(JjhzTLko;F!735EPKj7xD2I6#7?v$rP4JQqQSOU$2o@M3>CggWhmtOXnhHus z#Ycgz;=$;vo0^nWl$tDSDnCp-l~)N%SH9N#V)X-d*w0OnmiRTM9m|ttrr%Z+8~S5r zzi0LUzJdb=4`v1hslN-GuG~jLbk1tvxHsM+-e)!-xPa|NUhzntg0;36dFfH8(3rie z+`ix0UMy?w>{Au#y*yAqXLymd284zeX@XV4Tlh)|WmGeJD#u_xY{cv-gxeBHJ2kmN z5Smm)5EX-{PJk4t71p9@kEVk%kZ7&<8vP17MJ9oY59G*B2~Zd9^@bOF`9{2%GrY(- z9-QnFmc74Kgbqh8LDmm)hX?p@4xI^Vy|rZL^%c;f21SQUcOZ(d$uv zAQOs44TXM9cKry?utNJ#SHTNU+B?&7c_rI=>9-%dPPH;Eo>zo4O26Hl?Y$R=&ui1( zwU>8(_kGKjcbIpo+CpbkF0!22yK;Hw`i9CoU(eOZJ0}T7 z7z`~T?*x*AR`XDLVJhz;98Tq{mv`=bj>|iDHca06_CoT`7q!kUgF~GbHv^AT2I{v; zdFN4jwT4Z#{NYMgF7&))LkK;47(Ew7=-E1_L&nwNbV&U|Pp^G>VG+9ENosWUB&Dq8 zk(A|63u4KK1!dWi&-VAkk`GI8?dI}^kbKykU#)%a-rH;TiwFh#MfQk&?u*>?)kOZj za!hPm;qrCt7a@Y#H(7my<-!8~zE*56WD42st~&iyd!b;bgZWB@Gh_<}&v|}N zB%l0HbmLvAx_czG6(Qjk|QRiny>(P&$le! zPGJ-rf}w)r!ghbZLuwiD<;6>|!{9SwAH;NN?n>$|<&AiqRo?ux`zQ@K+pkUK8bycM|ga@y+&_sJsxb{7q(0 zMU5c0`uO9^{WsYq`D2H_b+Ui#mHMZ#_Kz*P{>JmycJ}a?XB{G*yZmGQ^Vil_N9V7Z zo5T6*FYPf=`DF9S_tGTSJAY}5Lf4QN5VaKa#s^>% z=3Mr9+b|T577w3k>5%88YLu;qH(<; zH_Mf;CoC6drp}k2AZVUKM|seXI2}5uu`4o9q4N?@g`w(41^bAC6o+n7VRVy&09D#1 zQ5xqUCY2WN9ij1F`p=n{T&=nW7+BC#=#}16s6$eXr_j8zqyh%XvY=LglUc>%DYS~4 z9dB`-LM=s59vDt*>zFUw1^KeWAO}Oq!D01k`;yiv?+8|%8ME{J1O`9DhczcruEb%} z_RI%ndc1Ab+>*yt5;ZXxIffE^ce(%0-p@OO5-5>fF7wWyxai0kl!r6o$5ApLz*v>Zj+5e-6%|xEi<|BWF-7i;lxC=URVGcmCsM!(c%< z1}!hz859oOvTdbO?eML)E)LUFeS~TYUj0=7`U3}pilSr24cZx$yo`e_JFGCVyyZxy zeWydoq}0c%fHD&n`V^*#d7yqWNy+i7vmR+Ue4Rn*P+HEQbPIOkluhxS3{jwlwlfjr zY^Q~>LKY-X84@SSQw=m@V)E1$x|IurbYz}xEl|AbrF42P11RN@RRVshSDI%#8>2KG z?wEzFfrI*tE1qvhfvT7DNQc)_KP8+y{n!h=*IMPa0VssT2bVnoUk0u<5qzhB2FoaPDuk@r{D|EXec~`vQVc)&Y&oV zd1p{8TRNnjmvo47U!+4ggCdficLwD`)HJI~Qt<>IyNU;+uWo8mR#9rQtf~At;;Fn! zSi17HXHW`Czp4Q{Y#fJqgRv7NO!!uZee!i5&|1wQaDg)@(~4&sNwD?|3Y$ZAJtvN_c~^OKr?bR@_AWekmQ+Q0ZyKnd zJA*Q54XA7OR7N$kr*aJD!$!=WLU@%LJ(O6!FRtw=QEokHTW!=#aq*X7JQ<$ z-isfw_1}5?@IK=SWXVnmVB&Uh2IUHD1$K#{JNxt6*3W7f!)w6qTAnb$g7k*M;R^W@ z#f(~vKY)_N^QUrol}#%ot+Fm-KCe_5<=lRfoWegqh3#xGt#+*Ne21TJ_x!NEhum!A zkp?n?5J?%RpPoyuZ^6K9aK3ezuASXVWw+ZU!N3RAG&!mRPbl%PgCa}j^Vg{BgV zcVS4?g7lz7L@FflP#rqug3}6;iGox{K`K*HELoipgpR5jS ziE+MZpUyY!hx8F+U6uS`=+K;#)!69LG{e}ilSizfmis;(gdMZM1^fqnSBPuu{@c3N z|0#*IjjjKLP}p?)2Y3388j_tAOaa^(^AsT`<3ojZT#kC@net%9wlCLzT64DdlBDa{ z#_z+Gx3RmwZDA##h?pb{ZuP@A?+t^WkG`X={6{|$`5 z687#bI^UJ8+gp3SQyb#>Zk6+0i_)+0_mYlnVr)%&RJ9?d-&N9Y{p;7mmq*vH2MPK0 z>uvU!sJ!BTHr@Ia*p}9_eg!sQORrze$?v?}JHBkZd?HPNSdyc*0It`5_D5bD?`MN| zcBN-EVb|a(V?j{z}W| z=zjJ;y)xdqct5*jH=<+T<24-9z>Bw^{g%(i``IQjx~*c}$>(+$ zF7|$Qi|X%6hRoL1->D6;{%+m%cU%8$3oAw0??27_?=6A;?l0{zQTd|>nofV06uH;A z|Fp$_v$Xzh7(rF~ET;!ui~B3S`ZA}xG7}k0Yn1^<*T?!u(i z-UVr@X9wC9&-;{W>bzwnEGc%t2D{_?E39YJ##YAnS1^}2TllOYB(#1NelFyJeU>ye z)~^ZX9C&nj^%onIG8o@q0fs||Gt%Bht-TeN7U?ulCXNa+Yq#6%y{tPvg0ll=ofh9; z0s6wT1GPjP-(R5t>BpzPe1qkzWH$-k)tCW0Y>SMU@t7HYOWa@a{ujqyTy55=FjLu% z6A(I?RS?FmY|&-|-MaTzI6iot?7PjYb7SqJ*R}rpZxSIi^nP1c|9b2RAE-p-xt)Xp z|6ulrJ>kzcojjMaAyc0{A-41kv)1&la{yyVe+t4T{l?kHuf6>GqUSGLe&y6tuvNAB zY(#!-n)^uppMR*4U-uJ?$ge-?gKI0l?wE+>R|Z3TABnU+(=dXGnfN{uP3?`4gyZ{2 z7C8WKV();sR!o%)*#-%pFj;y%FJX-DBVqmv55O-Zedk7g2_L+4OhhG8AQ<0ABCXGW zv_1=*f|v1`PFV}qo`TQoL@K9L{7sCx=Iv2dxd^ zj{{p;cd7dv-%uPcjqfKHo-ZD29QcMZ`(FAZzMs6hySliiF|o$c-9m-U<{Zdw;i!TO z?-s`UJ5{duKETn-WvCl$M%j8>&5YBU83aJ-f`XS z9pCpC(fzR>BjoqTZnsD7m&`QX{#ZApQm?%u=d0|S?2`UAF3$Pg|9QPh4#r~+FHD^;gX$T2zN`;@;AGIGsS^1kl&gj$fR)eo_1Em&Ui%W^hDk>#f)@%vn zh(}6P38pDF$gB#)x+^%aEVr_nmb-vNX8|*TO{q!iu|I0?6WWw6)G4t)YNICaOuN>m zRMfV*WAcGbDa6=1{-|I1jMF)=EXMw*HN8l$!t|2&oP()ZaH-cV4$+^|NN3CC)20zrE+l zn*Hqk1f%wzKDf5~*?Zp~?`Ja@Vtdcn-V??~2Cp-BayGj$``NL*XKe3@w&dD{4=rwG z35!bnP7jCga+qg$=t(izOYRGY7arM3B@Ull$V8%`EKygC0`=3yPFX7^R9B=4E_YKq zw)a&3tlHl5@p8PgZIoVtBqe2*flS7;3PPa@I>+qkbEc6i z?oMmS7VO>bc2lUj=ainEP;spuO|B^FDkr}$sIy}qEoC-AY`18aBxU6ACdlj_>ikwz zAn)(U6@Sq#9qQNhLwSiDycZf29rYHE`3fJ)j9ZwiM1dmy{s{Vu`!a*Nt6`{@3x3`3Ic8(4cjTB|*xO5U zMby1CRbxcAc9xY9=}#NvdNzaZnkI3}C55R_+2YH%>&eM|nWA+amy&a5RyVW?Qe3v? z(uXvvg3_FGX;QjW;>>r?pJ&jhZ-1iaZI9jGcEfh^U+4Rr_P-s%4qwFk;W$C^Z1 z`Thd6A+`rvcYC0{zZV_|NHDQQTf$7n{NESXa(c{x@C=jt#g`^JJ#bqzo&G6 z`^BFBADM{u|HlXg{GL4~DwpkPy8i!x74-l2FRTA6#*Xxgjk0xFmosu#c!7__r8x5N zEIRSV{=f^)+WZ^b2IjAUu;nP^=e9r>d z9XeH3Q4NY^4K7a*k0_=RgabE;`n2#Z-)SBqKi10Hh8#cC@kR@+|Fb6LXb;BJF1JD+>+ z$nJFS$j4jOs`YEpL;oNtUK!U97T|_X@H^Qy*5gm%V7d|Z=NH75cBv?9-<=%5s zh^2?Z3?6p^1=u-Z;loaRBfklSBSO0gVCl(C=I^RqbTml{{q(G_^-~ROGN1+p3;Qbp zse#g~MM|sgDj9U5jrzs)Pv8Obr0<{OzJJDicVE12M>sl2m1#sEAJyZJ=R^N*bbWaz zA-}%dV~?yas3n_BzIdG)Tc{NxiK zkB;|G5%S~x!}iGef?U&$_uy{Myv8o9-*WT`&%f|^XTG%6r0MRbzL&{LgaRkpX#ZQn zN?rQqCyv$73!#8s>=F6%(5rBKa2UZOaUU=i2T`q{Px29S#z$>2i9zTZqD{Te@pZ5BM*#4`H`K3JU?=? zJ#rt&9ZkoN1iI~d_>pTF>5_4?i_5F6yC0@$tkw6!)P{IHU(I?R>Z{g?pT0gB^V1x~ zK?LY9F`%{iX|*99f2$mS&BbQ8*5i?E3w_QW9B2oN%c+*_NZiY3L03nxh)dEi!)Bj z=-!9(Z#1gd5r0yfI%x~7KwR6kJKNi??P2@q!LHoMwcDQT8JWr*VM~#BHCO*k62S)9 zCje}p38-F}R_#m|c(0M?TFi$nI$mu8WplXS zW^lJfs|h!!Fte#(IGdW8f~H{o%+SC++5sm$UL5~gYC}9;S2{$SbXOPk(b za`Aw4C*ggV?IbW$&>|} z3!DU}7#Fg`o92Dg4o_hjO2+qLI)M(+(hg_F{*sq_v@}Xhc4`%Bk_$V;(oqqYtU50d zthrq^_Lqz)sJ}(zXFnOADm6N(z<7&J%0QGGvkGGNsi4$7Y`C=8snU7&pX;ShPYGie z`Fz}mhNAm{?<5r1ui7K_Y}=Y{KQP$a^J>Or*|T8_1esoFKXBNHc%AzHBxm*--TyX+ z`?DBmPMrp`dXPhY`ouhs8#WGmn{KCtmFd03Gq$C;RJsX4m^caBk9P2<^6dDs<++HC zwmI*I{NrD!k>`#PjL37p;)81|&)xXCSe`Rdo0Zs%G+hRAZsr>RTUQjXb3Nz$`;x*O zO_){!emZh6$+I>Sd9NXcqe=#uPqP=w5_RRrC~nt$q`Iz?O(S zW@3LqvA-ZjZR{_|4Gj$_hK2@GEZJ2hyRoCOWM?yL#V>tj;D(UwYW)Sp_RpCDJ-ZR@ zpErGLVf*J{+A9&OV_p0mz&bWqo|~}xH=DoPwLV|9PW^T4>W251qqlm_4?plgRR6M* zP+*U3kN79bG@btC22mxtlzbiM39t9xWH+Kcwo|jTwmpT~5ZhC%#-1YPFERy)Xe06$ zFZ<5I{6%xF&v%+CWG(F7nzOynH_gymw6{g=`=%L&t!|I2HpJ`EYSyFHJ>TZo=+@?^ z)P|UTS4qFkx!%O~v>!iQ=l4oXN7hGU&)71Qw+<`q&tab$I)~c43F}3_}ueX>stTYJloo;^?y3qaQ)eYO(e~x z?_4{76^`EO(ih%aqc0>B=nL(U`wP&tHJQJ`z&Ck4`oial5-w=3)tvMa%cFDngS5@m ztUrgQ`0JPWZ?a47$2UXN=gb$eE{zScR8||DU~g0k1o&4twR9F^Dpt zGlYjqARH2Lk(m_ah7?Ug%5iWye@``x2260uB6o6y4MfW-@`}%!Cp2rAh7u_kL*+X#z)>CJMA*8j`uc-`e|s9(vf8r5Wtu zoX^(!@Aukkuf6x$Yp=aGv>CB-cc-IXg>>WS$f)?H8V>2DIX%#yk2jfMGCXp`Y4DMIK^r^E+McTx8hC+o@22Y7l2qeeo!a(UFh__i+Bv-JMv=) zH`?;N0)aN^-s36iBl~)dSv(RIo4&n*8c7z5rz29-xN0cVVJ_mPmlIODbkZii%a;?H zr*z6U^2%rP^6PViPn!?ePYFqv;Dg~`qE^0U(M0LaxhML5xCeJ$pLNn2Uvc+~KlHfm zH>Z}~%6#>qeeLtr`*7v?>SyDV_b0AAebBcAA8*Zm?3;6%OPQ~>Lcd?FwbO3~SEk=T zj!*7GrORJo^Znpu(C;OeM87MuzjgI#r5up=+ZJt1CWNc`{wC|N^_e}3PvtrDL0tUKg%Km1t=T_>0Z$JS>)v#$;Pid@9o+JWYzgyoM{U!6h(R@l ziJ0b*&`v~Y`7kZ+<&8_9!W-FRIzqR(zPU>EX-a=rJO5gTF8^+me^qj%r!0)F-(i<$-TOp0zfUv$Y8 z^tURrv9)p@xWOaXY%~2wLSpYqIm11y`3#jebWa8JMTI>q_sUJL%A2ZvmA4G_RereE zSNSKSeS3aA1eLx$zmzk$KLnP(J!vPTZ_h~1K%t$DJXemhw^xgqWu7=Ct6hDS1C_oT z3jW6>W=oV>97B+9{BFf6u!}YQyViMh`gq{1XcUon&DxczS4%Aiye~HJ*lV$BK5E?v zST0jjbcts2oZGsG#;fcW*+38Vy=aPPSMK=wO~D_J$N2h$uIIk}>ChO~*g^Yh*K=WC zuF_&G^WEWf8^|u!((Cbg2NCWpJJOn`-I2_fWd?rUdM0dMpvkuBD(>9L>IM;A7V}rk z{%qCD%&OqjS9wZb<#q9bO6;i8$3n+O^zQs%ljVC!7o{7HZ&ZEp6Wae?orjSf?0+Yt z{jRk00p0V~6KuHf+V*|1tHI{RyK*~?u_1%nIOCkdIb`ocX&ZkI;=Jc!+mtD> z=6raSj;~3ey1M0W>(G_Iw<&*LrTm+w#jBfttwWc8x5>X(=KR{VZ(k$Ncl&Eso@XI* z-U8IBPn~({ScMS^pV-W zQ$>9;GE+l+l2(*See#j{&%M5<@o!)HrN5%)f znrR^%s+o?JMlqqHiCufp8Bs1gFxzurcC`1v>}2KE*;>E1cu(a_5GY%V_foHeYN*uf z3}LAUo=BK|6`q61nx2o=MRoL=FFjVC@ow9XI&rP*zs>K*pO5g;=;w0o$N$v6jqk^A zH`QXHe;d-)X78Q-_Fn@@R8Mo#0Udz5c*@o_*HgFye)qmg-}~z)cm2ffmDwCG#ZP9} z#Z5(T-~EPPbMAZ9)^!BV)_R*6UAwD*=EITqgBj*LZXE61`{l+vYu2bUhrJ&!j{MJ_ zqVKj(6f=d~kVO8Ko{V~+cx1A_`H0|)%ptggilvJDHJ*X(RZ~Y6{nj^m*ZPZh*8AR5 zFJ_f|QtQ>g82ElQTqi}Jjk+n9+ak@l)YSHT!9&7$yMw+(|6^}%ky}!S2 z=24$DF{^#2sasw-MAme`2L8~<5}YlzK%=h(m9y4&vbbrHQzgL?$0@`r!wqrj=)QQ& z&4%$&H%Yx15WW+_1K}n-+=M4A65sAjgWd+RnN2r(PR;JKhF2HF4cga%{=DmPH1LM@ zuiupRw98O2^IekM93CL!4!TFSWObTA=x*+t(4BkhWzp+OvfgaeOJJHgD%`V;FfbGUak=C04`b*EVk46RZ^X!*4NSaZ{AZd=}um`PzaG)s7 zE^CmK;4u@DiDM|4ICjbq=L52(1EfeNQoS40$L_xj#M^1z-mkN{ZIR-nZ3`o0Gxth1 zb64BC@=g;lz#z$o(0^avz|D5Gv`I{%spDL8?uE9Jf=08Ip1!b)^3ChqtF1<-#CSYs zS7}OW2!3tfm8K9#l|%h|&%;XF4|Tk6-=IM4A!FUbLglbp;|B+(C{#HiW=XjYxBemd z9!s)^+%>O1vuA=an3i~!(xt~XNjXVD!H)rt4~z%AFC;xHHx}+V-dP5sjm|T2jX*`3 zAtS+dqd*meR;dNfWndY24W$eLR$qStLG^_U4DkE!p(`o!+ElZFN z;w=3uK-jjblR|ac48uB7oc#ke(iwqnbK9$X`i{OA3=I$VqPeDU-+OT2Ha6~UU+Xp$ zFs+BIObxfkSZ(f@^nmxK=p&gMGz*AeTF&UQ;c&o|UCw=E=Jx73(||$Nstw}CLDEsl z%}5!dqdf$|*IX$7;2F>29eytXd)OxAfy8G)=3p&pp+v0hOGoN7yd*y-dnTi-DZf0h^nLa5! z(QO@-*6vT*N1v4Q*R|cbt4`bc>&{(ijO@-`&V|k>&V@02**krEQ)Icb zZG61U`RB(f#hK4C1jWuk_dH+Q+`|iIFK?soba3NV?l<`1p7#3<4&ci78@xO|dm7J0 z7k7o-Z}2y*Wm#_c1&37Vq;u8ChOw_M%;TmOY*J>8YDu-S%hJp=*DJ@)A1Sn+<_4?KYKC zzIg)%En~&ZD+k;aN65T;0A=%axajtw=|Yp|fuSM)REtOU4~$Y)&&T!;yojMJlpco% zs*T<6{|fdBBLltZa8G6W-~iSKgcxT~J=#!~cR1)YGJA&_^E(DcQ2@TEnBilDKg9^+ zP|cM-IyVxC%%JjVHFBHzfuWI^9~j(Fl{Z`*d3fy2C=JvXY<>QD-dEb^pRazoIsg2Z z`0QzX^1H9N`RChcg{{hK)tsibxx98+^m{5~YR%s(|D#F2{}7)&jn7i>6(;X;PHD^Z z>)itV4tAUeL^C*NV)z>3J2$Fdc5)rEEwD*B=pIVy$#xtR(CNgTH&r&x!=Bn#xve~1 z_ffHF+z$Kw1+x}&)!oioP2*5HebQj%4dUqZ%!A>KfgqwjI4~bM~_qiB2Ck5%B()TOdef%cRTX#Wyv@1`fu&?+dsmU=eO6zXHVnrTzT`` z%Yxm)gy53(c`war5KK1H=QZi~1j#qA##U%2^%LFsd0>iGelpI_FWy8S*`1%qT}7|G zaQ{lXFCmFaSNHsE9lG=Lj?B+b(EKv;O*=ckH0Q^y>=(Z2Puk^+n{Z|M;#=agr}6#I zz2er--wvy9RsU43xi27Jbm!-RDJc77oS!$`FMIj$cadX!L!-niJq(q+uXg?w@C_WFcOSF6W@Q2@1{cN{5e(Q!2 zNVIAN`H|?npBA7Wy8^nWxjhTudUsifL1}^*nudRQ;xXBKuly?8n?_0`-p8K6f!^ZC znOz>Pi4%^yS6<;wPh~Y`Yn9^B<>E-4wQI7SHt1|Fa!!PxMI0%B7A-2hus6{nVwB}U z_c-4{PH}F6l%De_NEv4dg1-OtsirI~(MwHBI&G%ykf#bDh6h(RIP>{#Fj{%bnlqnK zS(18)%d7)7&wO5U?wMh3A4vyt(ulI|%w4cb)LEWreEwl zbD5Cj%;j4@j&_@{xtP003YvQ{Z1v6PGC-CgXxYFm^84v8R)vojv!XH zx)sTcu>>j>xtvjwcG9fv1g0dCbV0IBPGFi%$KfLL<7|Qiv8k-FTPae36A9rBr|GoC z4K8EjEiLqx4tPt0mG2AOcOs|12wb$TXr3Y2y|O4}K{P0QVUjJF9+K68{#qn!9{A`a z+2)5Z&8p+TN1%q~CC~#OI=4oqZ;2e@DYsmi);Y}I09p6Cs5noxwN<`6pb4{ZucVYe z8QElr`$S*@XNEt=M{;<$2~Q1~ID2Q>`;9cZdzEl;*(#iGlGYdC<|)ylY36|bl;5iQ zc@`&~(d@z5;^x#eZD%yiA)nE-+ox4zWJ^}l1kx<#zDcuiMpNEyp3%I8VX#$!3--h@ za|QPapucUfm07j3Rb&nR zO$}>IKKAF2We&XfI65SXv|bJ9FCEjYMg^M3G<$Hic(2@KxQorI8}@#D-^`-InHhDA zq~=(TjXN=t%8oH(Qcnk37{UQohFA>93xYt3bixg9nHWkt|KsiSZv%ulz)pIhV=>}j zmO^$$z-a9)yhij4tk~W{8l|;!cRBcsI>r=cw;V~3e1$ZK?&h2sXEb|2pxH#}jAmps zWF3;MT1t1E$cpSLB2x*(i*zp26d)x45k+}ZhmbjoM13RsJPw6%UYB_wIJG6mE@gLu zkb$aZuYmc!j`Dtqd{05;Xb9H|M03Pe02-oT1&*o^@$oGp0>g{~{rO$w@3rD%&A6pu zX3l~MIkM~&AOD~KEs2kT8*MXeT@&uC972Zlge*;8L@m-bnKHs5+zvTo!2Ki|4|sgw zZQ*}^dLxw8-6U>og?d@1-O)R^f97EC!2X$$-YT|2z5U3Qy%n^ay{tc;-D^{%vNEyQn};+3Oi{@$!j4O zrWb|j+-hOLRBi@~^t=!bZv z|4V#gulDS;1{e;@RYj4hObt4+y_qh}`hY(NW}kLo_I7lNKZ;KAuKruOg=&Oj48_bO zbJ7G;(kL?$6H-Janzyb^5OtEMHKNv#NG5AUMKv-C@r%KTn@w-WENHC2>UR7_5kP_& zb0itRwDUQGIZe$p6x2PRGZBOFIVhQyL|!j21(m*G^zEqI)9}!pp9==Li7R)0Rv>lc zITY~fv{$nZ-T8UD^K&dJ^IbdW8`8wwx~OL^!prGCZ{#rTM?93S^Neqb`3nKF8jA)0 zY20bA>b-yZ=N)?~CvwapvtA8|%J?;a4fawrO-JJ2>9cau zh_Y@kC8QH-6QQ%~_EM5}+g{2)|5(~fnV>*6PwvLq$Le=1Ie#!aM|H9oez$0`CYu8K z^LsLXmy6+qLAt22Z802K45oXB*6gJ`a-?%FB_Y|`OBuA(yq8k-yChKCUP{0Ith*|} ztTOM3s3j>781g-qkzH8$Aml}Uahd?4nb^$DOe!qrDy~{rey+RE3Mpv8I2+t!IUi}0 zKEVx({?G}a%MZv_s1UOC0DH+xRy%LGYvQ{cClkvH55^@)|G;Ce)~O^<^Boxoo>8;5-C^|gg<>ojp&vY{JytvS57J#WyP@)HZ&CQc4PCNgQFkfWWws~R>>o!j! zTbI2A(jex(NrSL?B1<ZaszIfGD{Gj117}F-nV6LGOKoOimX9?8h?ORV>uq~M4yu2F-1a92Ui%vK~;um1WeXE6zL2b-qgq8U>S(F^S$$~PFKJ+ z9gXbpT`nW!3ohL`c@t@r&C3SCHp=x$Duv2#)lpL_>t+wGP^)?v;P zDS61`)FowCX?o4%ofC@&DS`Kh(6a0GRa>&Je6U^La}%!8zA`?08o%|-G-h2v`kuW( ztF7sKUY^s?yj^_{(_z>YAai|V>w9A6eWLWc-+n!Pmcl~O79zm5?-Q~AJEo<2JTB|s zPhax-mtl`R+mJd-gTPqCzUlJUzc1L?_3wu|*S|R1*T300$_V^8u73}A*T1mrRk8jp zSCUG1&DAVZy8=^_(YL?;eeE~+`uDXvw*LKUUL~f-FY@~L6M5!md+LHR?~>9jGVe0b z-!|93574R|%a+I=$?ozO!OwqEEP@Zxd%iA<;HR!>ka-)N2HE-|cqCG;c^YJcWzSwbZ>&p~H|nZ%2k=m*quGEigY> z)OA_+9W9VYx4&+uzrI0MUD8zGth+f&s`Ae|+n#qHU^SiVUD>5iqxf##JK>$QcpB!X zj7jwUCZqxP51cN|`0J{d8^v4yKTHtr7+5T3pWV-uh5fypV(G77G1=e0e|Dt5x_|ay z{{VM=_78H`XFs-$muS0hi4?bN+5EwT|A+_{z1W%0UDo{yANr+s{r~%LW&Qup#^)7z zKjq(u?z1)h|I2ck+UENICjFiudGQ& zW*o!bd)XzQ_Sq?tjOxRmgE;Sb*g2LxuiE9^XZw|Z(a!Jx30LO#kHlwB<0l`z;`Y1B zy{j=vzyv!wMwdS%QDQ40eR&CSO={H&Y@&%oK!D zZfu+G_l{SyBGbX*2pgBXIP?{R>|f92niW`)maH1%KVUfz7ngmX@k{61+y7~}a{K@3 z2mI`5{H;%2aqa(#%NXy0J`JR8x8=``d zH^wHD_^J7}ObyPag;eQmz%`G0LZCYS;+$YQyc*|cA*?u8f%m3+Wlj^dr}%?q>FT$1 zy;!jex!98d%VWD-Xn@;d6XfugZb&tni>(38Kj4w&x@Af#s}_*)Y|IGKvY4gyGEU4k zjB}j^h>qU*P8 zU@qv~+r?sh@5d?OE2mO@X3m74`MDj{5f&&zoTXe)B1V+Gn7vH1w(N***%2w1Z7GL> zijfktCLzsIYc)%Hwl{bMx)hS29$nhTM-w9riLa=n`4Gu61uF#RG@E-1B2QPMu)9PI)P z7!OF(5e6x15@&LK*%R5zG#wSPua|xiDVJ@#4BIYqb~(CBK-#6MEt*3{dtVu}*m3g# zud#q!t0bgDg**3^(N4c;lOp>?7;Q4t-X=QQ+l1mhd?l$OT|&3N*(N!>*(NnJ&uuas z1!V+Nn+#>Ih}NDdm5k{6MWkF%YLmf|?g{5;6KHVXz##c`gh9rt#Hp??yFYuGrlUfX z9nmd2BIUAelNfPUgZw;So138$ISn=5_V4}zVcf}xhI5FQ{Hfv6&1UXc_eNyQ4+C}_3&?9@RBr~E(X@mi1QJ*ysJIiG?Le} z{^S)9ccsKV=iQ&(<=L8QcBlNfw@f#77p7}Y&e!j)JC63Qyf5dTa!m(1Wa*U4sg#<@ ztuPnbsunPv^tYx%y1_0W?$PWXj(9M7cBgbOe0Hb&j;5!)Y1Bw}>P?K>krIw7h*bz z%qir4yUJ8VjegfM-n+dT=bc7teU)8(l>@#lFa)KmeHG@~z6!PhwV?aWAgjTi_I!g+4<}kw|b^}bnVW|~9R~gj zcwi#`0RVRD0(5#CzhAw5{!i?>6?^wbe!hKt{54#8eEj+N~!N#e5Znth0 zh}rGcOS+I`!WG{)9BdiDdiMyX{o(%+>3P1@nWIKX;!i{WBj>WMDLl2TpmG4_X3dAR5 z%412F^nhKq@lr!djLBMg9<02eD{eqOPoDnp`--3KHurG@^129AZ@HDI5s4^3KXf*_ zr_llcB-oM=VKzZdK!^XD8<79SGg72P)eUbze%0nTATLG{NApow+Rj=)i&$(yiwVLL zmA?6di53w9q@q0NIt4LAc2Y>n>403D223eG^RGpvf-LB!wsoLCSHmay4iVleA(_f4-h_NYxaWAffp`x(1Hv6o z8Uatm&BylSv%1N13D`@X;=dSiCLFR|Si1 z5Acr}7t&7%!KX)m(yrnbufZ74PNf@=!K+_4d3tGZiC0`COrAaXfzM-PQ6Au5<{K|A z%O;s;6N%DXNXG$&hC%$y)Y$%-DeyZ1BK{1wLjDMkI_=a=$YN!yTai+M4k{M8lu?p) z(yZ+!WJx6Hf@GTrsdbWc2&v|WkSalJDr+o@Nd<(~@P^7dZE=Ik*mz3|y`=-*(x7=0 z^7eDu(oM)4kZfM0A=v`ZUyEeTgV&uT+x!^6S+yU$j;tZmN&F#`CD=)}K&BnK3Aw!K zI3O)C9hktGLRci+4*%iL5Rt=E>+D2(U;HNI4Vq>`G-#SBU}~D^|W;Xst_r|h5HZlS}1 z;R$P|8<5dHI=C@9*8-;7j5j*ZKV-OP%z&Ne`9qTfLdPSl@sEFHvzw4NXuYZ^(t1Nc zf9X7bB`VN7&)>;=8{dXvMja!mIhJGNPK=}y+=114I?%!p4zM!B>41oVK#Fw24R7Xh zhhyh|yq*3-fDUgrlxU69+2KvdM!1BVkV7V+!OohL2GQM|Gvhq}YWQ@V=U4nYHxzZ? z%aVJFgD-c@y%Sz7S~$ zoZV7Y$PQJVa8KHiP~fN-ju?3xP=={lM+@V7IDfAdBh9l-jEpRpkgIsF5hFiwQxYQ+ zj^)J-T!O3!sVF%!;?7{!(IW~JQ5n_-!2MA3P{8{J?f|-u8w+@U=-}h-W8O*ct$O$I z3-)h(_i>_B``yPubE_UB0eeV;bfB1&6ul%nsv@Kf7`kk|{EL_Gj^bW!q8i>dtu1AJ5 ztO*&f!XGY3@nDh&&*}qsdkMEcSL;)J@BQ)v19>G1Fia zP-e`MA9r3Gj2T4U6F`6BRdKY%uNPQ7hw%)$A(P0dF%lDYzHLZb&EG6#{>;x}wy&6d zQ*SZ*Q;gK_=r_w{{KGaTU^$Q{GY3b~Mf@E}ohM@H#{QZk>8hu?DQ>waT**;3G#{6kPy;OnU$@`otUAIOjSY2o@YHt1$-7zIf2LP(VoL?;pcd54zpDlN0} zuXFkH5t+BLNRVj3=5^2lo0Xu&g9E)m3)E(j5x2bsL?Fr;4+3qI1li)%&FnTt8Meu2 zlm&tTkvIdMig3AYTsLh#9T1uagysREIWT6=fGDDb=VU{qYci>Oz`t6(BDOA17kFT z0nrZyL_ZXeIs?TqLB>Db;5ZbxLQ^0(3bc)TY*E52n)niWemx37{tXE81Vj`OI-mj> zPzR|)WBh##sM*iKFXDW}p)ggkx0tzpAP$ZoYBA0g*yxOC%%M>g_9ub)k3mz(%U~M~!Da45#&L0U36HwswMyZ@OviTIR?h zpqzX%Aj5-@^1>f{hQm#MK=CPrlygFV%9#s@S!O_n8KC7Q$nc_@^6CLm!~h|c;3Lrk z{@}9~XEmHTCh?aH0eAhm<4UhVmw?fPNx^75AOmH<`vwjH#VEo1B3z7?_1{B3heo-6=m>B)cOW zwmI$O2^v72^BK2*NB(qI@!~HQdmeSXm^9?b=ka;e#~t5WXXSIvxlJd=LD;|_Ts-8b zET|t%qkF4d{Ld6P$YPwd#doK1Io+#ZW0)_r1twk|e zt;xtTl#3nA#U{uAor_)0#V!G5m4MlV|Ac9&T0p9@AlZmBAN~w>%$R=CuBQzm%1N`K z*!h4^J7*j2JUGsE`o(}Yo&6$={Oab{HlSs9`+%0Pf!U2}pN>3IpFW9Sb3p5e*c{NN zEHMvglTmucI$11FWUq+Uo;yt*(e;Z+Z6*Gks6Z7_Do3nTqZrT{DiNuLidaYl+h#5|V?c_S%UGsnOjiG8JD!L%#|K6&i*_smqJ9h< zXzze&VjAxBi*_8!ei265^XAvqj@jMbjtQM?*L?8 zSf_UE&t4I&J$Hx~(e;Z+Z6*GkSVKXnQ8rYnQM6+s62lC6cLb9pkc7y+>rv^+UM7on zEGz0LQu;-t7Aj&P5p2h5ZpVOOVM)S?@GLu`TPGBLK6)hsbwH-{94XwTh|=ko#ER^< zoYPHNa$D!Y`QOEAHc~BsSH=Aq8NTnPR#bq>8bJep~M6+qm=1i&T zqM!Ojq+C!sC@@(zL&BU^3Q90IkftMyfC)*okPAvA2^r>O_A*UJg(y3sTXsasWn0R) zEi)Ax_Uo9a4YHdCn=$eIV~# zXz!d1cr@AJFi=N4u#I?dPKt+mCC4Al@e3j02~xXoKYOwG2o3IUs(9KLiy1t~G$lT$ zZFbV<Hw#*a?Ms)omQZ6X*+M$x463)SE z(12DWpN=rtV^rcy=7JJ2g4ZUpmuc3P9nmd2BIUB>wL;!GA8o}59c_8gbK7OJq@lcQ zmtl(@H-B&&3&=V#%0-F_n8xwZPQPfEBKt)c?NV!R7ai^G0ty^sh-yT-gz{jsU2=G{ zU20~Y$AIA|C?lBKWhi?^H0e-gO1ngK{UTB>D7DLQN&AFzv~(v7iYzmzB#7OR>ZR*Wo0g2m)^70MDeee9P96aPQVYX7tP9Ql>at90C2USz5k%6jJHCxpX zJ*HvcJ8v2WzSV%Nqk#&~38E;{y$AIKo8Jo0s}@a@kH>~OY%Zjb@MkPLA0$D=8UE<8 zBOdRv1jKMLV74$IpBiq`wX79N8V|;J9@(S%?VCzQU}~*~2VD z#||Iv(0TD!I+O+x9ALY?pxP|JfJ_nsp9F(TzU37AKVA3z{^_7+HSGJ z1xZ)9Wa0|f7CsaZt^l&|y$yh2kD%x9_lsUc@M5C0@urt?`zrh^Dt(pFYG36LqhqaR zl)6EC?oW>#?O`z-Zdx-Svpg`VcWQiz*Koj7`JsgpK3W_((_75^P3;*?qfuVuF_`ll zOl6ou-5h3aul3#V1Du+=y~^nlPEhS)@A|>^!yPZcl@E99iqD?L%uD5bj0`;M#VD=051Ec?k5b+*8&9nyl*28KM?#KK1rwVH7b+ibLUYg%QFXDF+qUNNEm-k>-?6eU_7yX3Z6(i}9(TaD14L!Ir{#1JzV7r; za2D94T-Kc)O5QASvgPM9K6fyk9$KbUjtec~baq@y&R@&Ux=zeK9}_qE4d~DBM*c3% zJ`)B=>BgN|W|di0PcXsr3L6$*&0P{6EpAfuDF)`4+DQ<3R78fM|n-;4>c(0sqd&SPpO)_;RHKbQ3P zND8p?j1bwzZ8lg}64too$I=<0u}c{{>!L{OEdl+dGeQedf#w;ZIh+?8JRbHOtK90=l9wCy;eXp&o%)uvS8wkEIW^v z|K@H5MAU@lXuFt_>L&C#9~4^EQnA^7cqa%04s)d7pN(63)mmOfVxLmS{&IW|^;h)^FYB&jYi3mb24~@N; zoJ%^%L?_7T1ev~%#l=GlNP)BlQY+D(4rUwp@h-(VEsH)Vc2_Sxtr z$tB+BbF6^4t-BpgXrJ)r*S@3oM5_HF6c24@tl+DNf9%^ibM_~kIeXi{_^q=qc-ev3 zJ6_g%>+Io|ak13hFXLvcfA}&k{CL~T1`o`Rylm;f?7^3vJwWL4t+V@I*0>eF)m!m9 zKQi;{VY@go^GnedBaq}QXpBH2$z9CcAJiWK*25oJJRnQRfMvHW)di*c0QEWH_R0j2 zS;LJqAl!3&U<%UNOD6&{Yzc{9k9euMUArvj_@g;~Eyo`MO3zi`$n5?3w9Wm&vm;2> z#v4K6w!@6f{!;YIk=b9*{gT~N)6?4H0Z#=U3OADxAL%I|bQemefY2!*bP8B*AV8;Z zL#KeyDIjzTSSmE26K;p~69Mx!gLJ~}=w-O|HXWirI}D@jFtFROZz#x{>>_w5qL76LxjZV?506X2d98MztA}TEImptOq-kruKc6FeC)BbbJ8m!*@QB_ ziQd!Z=l9BWM7Q_x{pNjl?EAreKeX>HKlo6`eSr4NsJJ-5VDkYVZ~f9Q{G+aZ33{U? zTD8qO^h-ZAe!2Rk?}?Oj659}dI2mopVC=BP2)+Phy?ll1mp=5p9r~pcIQ#ZA^-FhU zXPvq{e)^>!|I0+dL`k~86zuAky80zY@TP9Nt6#b-O;uOFRBn#!UO65^b=F9YIYd`NK{w-boQpn>;chuD{A;yl%$XA$t>GfZ-b9MDg zEcNG@!d#Q)?RXkYpRbF4>4|=C?**}VZkhazZ1+Cec<=1s+NaL?)eu+Jm9Zrq3gi9h zfioVvz?iZ5z}YK(%K??I299i9_cpG7|NrDzWjvU6*!Rvx2728%svMZ9@2$`LZev9J zkHp(&u5{4UeIcpYwsQaV&97`Xk=%nTn@B!0K6(F)#goNeLGJ^+(#nF;SlY(8r1$Uk z<}@|a6j)ZGxGdPbaY>Nxd53-{h&4hZ4aReKHUn*jd8kTG61kUh3@yER5sMcBMuJMAqsKrAFwh`|QR=x}E_}=N;E7Z!`wCjuD&ykty2CNa0 z)-dYzTmx|!J|)jyYZIKT^~y~75~QjVAsTs zN3BYWAD^K6o&N9@lu5N{*({Q8`+n)9gRzkz0hK-|F%CnFV{Cy~MM{bmhYN_Hju2;b z@`(Ci=9N-~n@3j^-L}3zG&=7Rs#y$bq)s_3M} zPsngP^?v3eC6CNpKVTn#MHpse)yg_2wY6$x z>ui4D9UBMHO4i^;mGVcJ~SS;D^D%{Rx?ukIXi^ zOAsYzJt_l?H~#fsc^=+u$ZPW4x37;)0c4wEzca^crEq9mfjqW ztURUhSI&Oe^!2nm?`HLi)T;2R@9lHFxP5w>B zs^#{4DYIsi%40|?*E+yui5J;b$;7>>B&n49jcMcBcro)IORvZ!tLY!UHSHCq6xq(L z94eWEYV;svpw|F8tfrrd)pQb#R&LD(&?$?I1WAYNd>|FD&2YLc>9cauh_cRhI^+>* zlQLRoJDt4SY^VR_OVi%g1O;O|eIHKuw#Jh4N3wHNC#Er_b;uNMf0{Q;=I_!pCSh=2 zHi=&@ZKorP!L*;g#&-I>f7IRrB@tS-(}R>y+D=#fE(yfrUge7m{rb}e6<{`$?TM%* zDG+jzy%b3!J0xDD%+h!xWSwl0LFQ&A6_#@qSFI~QmpEsI6trL*_Td#c+=pKjVy`eC zY14ka0%X4%bqJ7^fZSdogls*)UeYMEoEKB_TrJB5!a$z}1Z(PVrHv`LD)|B>%-4XjqQ(2QVRSQGxCO8p&TA{YT8aKLg#E-dt2k-`5vz-YuQfI zv@VmHMhppS+uK@XmRPm&l-HB-k#G{M&30P)NVlNZCbrXN(_#H*espc?W;O9;Is0^Sm!X*<3BoXWkeM6@-w)5x)<(Z`1LTFUfV2KsB!t7+}p zNw3Xqr_HL}+BLF;$pCy~H({_4Hl@16UkFdrwzc1}=UDDV& zDyp>J7|>s`ovuX%nzqwboGr%N_KHxEu^bO~V)T^YF-1~O2Ui%vK~;um1ms{B zkRqL7!<+gz94rIzcD`36)R}Y=I2zeekev~*bbb>?bkD$i`Awuz<8|(?q#{Xriq;Xw zUW=hYdVtMIGTv9HhEK=)3X#!}bx6V?V}Qx2i^+?bikO90`M!cFKuVzAmsaN}eN$g) zl}FG5G2Vq9@i1d0^ZyjFl^3Q>Mb)D){6ic@OkRz`N-;T$#fia7%t~X^c$PF>!>qJ0 z5h=69Bsq`iraA#b9VMejBoxefX0G8@Jc)|IWuv(LDR%g2E9obyh42ioaG@@OqstOf zre;zx0-01zY2w+Rt%foWcVFcxeU;Z$G%20aN%SeBgZQ$LWM_d%1mcWAgfT$~o3h9? z=RyOboEZ6&$TIzba1^`ppK;jerffF)#gqMt$zUKV7V>Hml$-)YB~xpGPcg0MLYYgN-obU(Q{1rmylhtOBWYw!1D(bnrm}<>NdqFg0O5A4C?D#y8HRO8j_59=GeSCD z(0BB`QKj-cP}(g(v47k2@n~D?HWc}#OZP*_!0q+LHnnGiMuGRH=sr%U)7y(9n3k*I zwt^26{$3h!F80Ld1hu zDLk2iTkFM0$1%u&$@JR%7Q&rZux-OnD4(kfyO5u+IoEFujOovB7c~BC(HIQCU@F|m z1_ARKZfay)o2wo$S>(s9(hy7H**;>J+6PfQ+{bfsfBl*rWo;fG8IS4?j40F(51>iD zd*G}*!5;(A%`eIr1neJJHQssSHPosv7qhP>@Y#63n*YyU%=ec({`!Z{z50^%4;^Dx zn-bwom#=?#!H()5ZhT&c{^5F@cB3(S+u-j1_T1#OzdwHZhv)r%;`X`V?ymkJCPL=1 zD^dSYj=@UTO>F{W+Ys*NEW3UE!)xP0(X@s4ji`Tkt=q8-{IQ5(>T&(TtbQ4qjIV0Z z!HC+SfA~c7v{>&xk@|-RXw?p3XNi@ElJ(^)A)cSB$GYz-Rze)4dA}}7h^MY;konvo zTUSDiM9Pb^)RgfMb5s%d+lETZtk$tq?P_wuH^jXfV@jWwN)nm!);bC2x3Wchfr{kb<@zI^=r9hHy&<s+(U z+Yb;hw=N==cO0SNyx^)UMA)4Zk=wP)UFkSB>~1^b9C^vo@tu3R(s9U)Pde!sd&|(N zqfZ`G`8=eK8B?XNyFLDodod}vuvfU>O>IoO+33WT%}VojBY2Oo&BF#VTXFZY$v7Q` zqp_(Hf7sz;uf;l|9`5|BqcuJ0xp5?elQ9%m3N= zILrTB|KFAWX@v7GY8bdzfw9>K?CQzw``@T`dR;xagOj@i<+2LR&SG?vWP^kE63&&b zp1fSiUY4<5S5NK+uol19oV#UbHm0WKQid*sn}Z54SB1si9WU&z1fHBLfvf%eU~dfi zSNHiV>(D)awcYbqOmr?~2a99*)>T%J@xE2;A0%TzkIy%swED^yUE?gLlR~@y6Rvz; z!$;z?r}1~LPutU1(0vWxYAsn6kEPk;lJ0AGQBG4cO|kKP4M)CkNaM18*WOk209h@- zL{+ss$aXMk3CJKH?Iypt-XRVybG<`(y87^b4cPh(9d;RC(Xw&4G5zHwlTSQf9f@?A_>M>+NU&8jboT=_#hwsAZf zqIidj7N@ST3-Lk{85Q4+-}urhHa6EZ^}QubeXW&7_fg9WnEDP8Z|lPwa=dj!<18UC z0STHEU|QIG8I;)-;u3MZ?xS*jdTbz-iZ3hzFuP%kAM{!#O)sP zR@mgGsItgtro&vsO(v-(2pbMR_(?L0ZCz24FKPhkNJn^>vchH45s!O?h4?3!^x-9H zrJ1l7js!|Ad(nHo&c>qx-b%aIJ*5X=rO(=eu>C#-7w3GW$FMmXw&Dr!m8w zFZU&3LwA$epJm(W)wH5ZkM+D}<$uS|v7sjJ(Thjr)kqEjS51<5b%{^gWf|x%9TBPX z)wXdrk5g`45N>k7&Wt)X?w0X@$NnP#4iunud<`1O*U&_^Nb5bWVHTs(LLh4f^ox{=D?2dGFwnzsf)x$sE1i}W2=`|iQz2&S%vzHI6GD?M; zQ7d3NRUm^dg}ZZ=Y`LfPC%?Mz#%%WAeu2om0W^{kn;q)#wN)*@RH?z;1N6L$+K|(aiH$mvH8~O+OWO5?H^Ld4` zo*B8~MmmJyts6YvD2^Ld%wqwmty&0KhJ*-Odecm$@p*5Y#w)U=_(h_ed&8wR7-uS$ce?7VdmxhisS0ugv!0XQXUT;I~P()tqf5XWJ7noq`DE zDoG2nEv^xsitH24pEGkpdUA;MNg@9}7aROSV`~*`%w=MhsO=$+H>8`|1KgE>oCFVe ze|iHNiqvq+^i}e(`YO3R;H?dT)z`Qn@H8G}%mK?Xe(!p#pDxLly5eJ**2OXp1?0d| z!29#DK(t1b9b>zh8V!4H4&l%GuW77(INKD#@IJQpxE#H8h;RcnAvR2ht}H9J(2j||I!`D z|8&M5(jS(x5}H4SjZlxUynBdPJd)NYP$tdeZL*-~Z{2CUNf!F^ULvm&&LtG(in z55yoB0?2JD3e4B?!I)_dNHVW|bDnTT6rt5f)yeS~Zc=q}J`A-}#7kG5+})dKRKScj zoXog}AJ5`#10J#gdw1!Ov2iR%NYsdn{;U>tyT;(6n7yTvrQ4abgq*#lznFb}wOqhi zg|b62*iPx(6FsL~faIYD$)ir7ts#PI1X{h;2(&k^`~rRov|wR3gT9BPVkAd;s+%MU zwDWw*V792gB+v$VOG`9xQktD2bTesop0EyS*6fgGKL9|Q6)+au%)oFnWR{+-_4dx) zZ-|VL?>9sq_*kAVi}m*#4)2zlC6nYE@Mz8P@Xtaf?pk(R8Ob*vFv-UryZ5JJV>!*_;MTm2Q zyRXIR6cRGc;O;;EFUc9A4i_E0MsbM7R)F;9_h9}mFFHyXpp7wZslH5i%Y-%t?==@4 z{j2vSoTto@Hb_Y3i;j+6&q#=zBQ?(OQ0%emrfip`b}F@9c(kZL;dNjhtrj9`Ns^da z$6O9ZmQ1#czNN?*XPr}G5izr-TuD{uar?6$E0fwW$E**ss|@0WN0X6;<$(TsSPldk zSQiIm7!8P;NJyRwz+UpSk|!gKa)41T5EgnjAR1MF@iLy_6S~P`$_V_#Ym9I4S~bpK zTE9#85}#%LNxKA84bmIq)v1A=fDA0^Hp$bM2}`^h`jO`{5PUujC@(y!C$EnJ7$;a# z_>o@gyyjdSI9$|+pP3rlADN^S_)UU{Kf|q%KfM(tc z(opmPp^+u1HrWD=$_tO^$JS*1g-29*;|q@>#h5go3@kFlJsxPlkm1iPpTomVctS5R z^v<;OZ8W#i*H~^3Zn==k&G9@C=xCHty=as%U}_YUn88Cd%IiY~zEPvJU3g>;>n=Qs zY+>3%1kxbpzDa{{;gKx8?!u#sanq1lf_M#>5HEY*mbuBS+PNvR26-Q40eMTfF!G(| z6LsTntHVx*fx&?I z2PCm@gA0!;`ZFdAJ1;yU+x%FpVT~7lZ53rg@LnuNE;ttSh$5|50s2eAe`37mF_ccm z+xWsGGwK*U&9NL0cVhIE;1hNTTGYW6hHy}oAx;EDEeNDYXV~y&@^(0w1LEy`Zv*Ib zeKxG)M0V83&IqhgF8Ek%h#yRzr%gv|=x)xC@mTCq_+03**k#jFisUm?OUNfqeN@BaL~R!}t0HbF76VB(D|J0FXE=evn%6Nrt~49Fv~3W<C1?K06s4CvJ$uaoO|RFzNfkZ4 zwe?OsGF#?sVgA|*n{0Va<-+u$Fr8a1EQlA{4Ul%ZWz(JwnAH;yS2rxaoP+x!gDr+Z^ntX--BNXr8|A=hwc}|9I6m z#0hHogzO)_`h}?)v4<3^`0ei><|RO?$K!GT#?61airT$2MaWBgq$GQwB9%Q7#n)>9 z9SY;N`{}Nj`|*Dpb*4B^fGp?l?#HP#FqWJ@lbwz!kKg@tKXnAUF_MJ<3tfjOt^Mui zLEWm1p|NwMv`=xm_ii{(h8AxD!l*8;u?Xwlk1sn7IS`;O9tLK0@v19SgxnsyO2SF7 zHg&NJUDd@y!cEl0X49eYFh3-`?!6nE%gjsAtX*c-t;~ED=;~CbGP8}wcvNYlDBg>9 zd%WvFSF3=PQe7OgefNHR=+jgecQSEmls0woJj7n7F4ofqIhh({Vet$e*CNBe*7m?o_|4~U1LY>0k6#U z*N@+jD!TLQ&FPo{%L5aS$NJ@6pSk4q3mYBfwN=-gYg@luG52f##cy}4U;Yqh+xq2q zvU8Nt`SDx7eCQ_~`@8Fx?!6(b`V~!kR=W3V+a;11uTZI2kCYz^q^(K$-TSpSv3ZGQ z&$x>st=lAaK-{rGjVhoX{&~Zi@Ng5J)>z3laAz8+d%t#CeWj;qs?fcqLfCvsJVm_j z{o1bj$CY_DFnk%)A5#lqr{T+3Hfzr>eGUAl?n(w0Jf2Ri(#Mw}7>bfrfTD>`cSdSD z8rgAGy!5s1JjS>y?Q}HRQx_~Y7z7Wq=Y!4uM1;j@lsIE@*wZL^KZsbHSlhFxl|KHM zz&|Z)l8Iqc8fN`r-5{cC9^tRtBv&h4p2OLbHhh&rdql@2GcaxRVvQFlV#XUVZyLbj z1@5LbQ9Pry7)7KU>iJs%!_Spp=8tbUjk$CW<1#Z)-5Ij(#%Jp_4i(inQg_z|XX{J* zuG@$5vGu*bzi;NzFt{h#hpofb57;vifozletdq{A2YrV}yK`S>go-aL_Pxdc&V>CQ*o<%7a~!J1R%oFSw*kyDiJ<#4Yn zeF|?%pN>$B&w!M!^AbyMyB|J~y5jZ|{{f~vkI(*T+Ofa<^HXs@qe4+|5eJrUHK(T&x_HKyYdUBP+l#7{EbBA z{XIBG6~Y%Xfev{RVJOR|K|GHnBYcI>)fCr~TT8Zi5@tbr%FjJW?*2i>{jq8omQPgs zD%bQ?Jn3HWzlWplA1jWr40xbIg2wx=WsIdz%=_bv{b7^F`5ST)n@KNKF>egyl-@Rd zJXIkrzjt|+_SXus`qe()ScmR>v#t4t^Yz8d^#gX(Ub`vhB<}WRyb*89yKcbV+3T$h z{RYyTV}0bcAS4)Euaen6P%Vz!Jy2B{6BnR8$He4drf=m+zrn`)9&vXmB0gMfaGyeh zMt#!S*H5{!Wzgx`*N0Ksd<;U(!+=vbRgBIFFL06H0^#kpLb|*5^|kJs-70sI%BH-o zAuD;%dxhA6g6p*NI`QfO(*dWe;mz*SzTWzI|Kgw{aw%lRu!~sYFaR;(=0Q*-@8$qE(sGk_&l%4=!HTNZ<@YiZ#63(_kN1Hfp@FsnZq ztrm>jTsu^8UW!qLS+%bnB5OKe_nd)b<qZotIzHUN#X08EW?Sx@_* zlj~@Z$d(4_o-?={bGT`TWV9X$=P14W@so$2S|q_`PHt?8;B5OnjtcX4<897g6gJ`BP%QPgeV}_Tr-xtJ>+A`4 zKGjI}^SIL+E~m^KpF{Zgx{I8Vo&fr1x!4ljVHR$n_$olWku#VVa`?T}&(`);ej{gH z1obMdMDDu=J@mVmJzW5Y!eaZC?b{xl>ev$G`5)b^rRPjVxb`tCZ+k|H6gY1DM$Sv} z8#yUT#~V4Zs|#Vv-Q8)kh89y61ua@s`uSf>O%O3aDl!8ZcX&kRWTe1^DyKAuMBjsv zGD~f3jbDU{fGZDyP|#;(E@qOjws19Ap@kH-J(JUR+sO0tOF0pB-Ag(1))$;oM(bY6 zN#1QQAwCh6*-0KmSw7IjWNj(BnpVntTekKUtj=@hdMtpVuFHPT=2m z0eWPCxud6-+YQ!dzS21QHxte!F%ptZ6)b1o?u++$vPnYS)~TAi>e_&9S06^HN!eGL zBt(>wByA=M5w$c)2uEp>5Gm5#08(RF7NDxDK!0J9z;~D=5I0N`aED2PP?H4x>85j$ zF(wH-0}v(&0WnDslBe$l^pdBQJRfl>C(=?b5JuwlBEo4@{jId|#H+5GJeG{WFH92n z4wD4(jjIoh3*Ae6d`rT#>G=f)i9F(s20T7c2+7kIze>Ek+FYU!0m0`LfbyjrUs}Mc z50F%_essK))2|OdGc~rqW(xckGxCO8p&TA{YQB^+*&2y;@-7B9Y zUEb1l#m5%=JU-DCC^W7<1ZI|4Es3A;i7j^h%7@+(ePWAUdTn;~Az__YACezRL8)Oo zS=~^a0$II;H!4IaVsjf@##XaP<@J^bY8C;_mvXkB)5T2^E_XZ{j^H6&eCY~23I?V#mpT~J>`n6qsh;wbDv|oKl@cOF{%|ep|APr*fn>2`5 zAHu@xUdjmrmw7PS1o0`e1o1hbzin`nS+#RhWDWAu_=CJ9Tp0N^idTCk$!*@t>#);# zY-%$}Fi-S2hvbvQH^=kKOc}!NVF+Q3FCAa2c%3K3CapIrso$&E?zLyFy|0ne% z7luTfbmY+p`O1c8QfD${8-M@7j{v2 zWHe+Ql3Y{@FvoLtm1v*Hxv1=|=M>b@gt!#oO??vQg=`yjZuKF<|tAt0WApkaG8hxNEkICLgPC`1csRa`ty4{f3Fo9&9hBtj4YT` zBFnZ@jtGsn4HC1&jcO+Jvb=j1eo613MgL9X)jEo zl?_T^ifFaXkTadZ!>DIL*4G!NwMe67fayPfSp$YrEApV0jyL-hkomrrNp{vAh;FsS3Xx< z12JhDf$`^%DcMb##CRIza1WfIc0tdDYDabwY^+ zpp=N?mfx|ki}B-{b2nxiK-A_OWsazi?CUjVzPM-lc2D2PTMxCO$|9q=rRO4U;{TK` z?McOVd7KI^ig;jm!uTK^Zo_@sOy?8n}F z-zD3RT}*kAYF{2o^@ls`$3}445$%BJ zGX6$(j_Oo-VRE|j6fnrm_hkMq=b?l_dI6$s9*Qgm(>c~P%8N%{EUt-NoNg2DYm^t3 zoX%6!9U-ENHRHA^FN{Zc1z0LC^lFl%O?eSfOXWp4O65hQK-2_Ejb-X;wO6CkN!mWk zclSI+ScIvHQ226m8S<5S4+$@%ydb=NxfxkA51hpx8d-wF z{}yPpBgzYlrNtJb_^-#6r<*7*F3>3R;!2||08^uM&r?iAJEFV*!cB8JH|D1N=)@Z3 z#kRZIw4IohYn$>SdV8t72nR}wjAuX)bTQ|&HIe<3yHs98Aj*qKjPfENk#lC_!i|&{ z`ZFev0XJ1%5Z3rxN7pJZF2Hu9VguWa0sVE~ZMcHpZFoZD;dkA0$?`D#xZ(Z(SIl{e zH{a1A55F2`n>_r&>>Q<+KYsG?hn`Ch>&nAd)%oI5vQ(yPO1C>N4|m@IuxspuWZiuS zp!*I$Yo*e@qlaAcK=(X_6I}N^#S8B0O2)B*Y9Q&WoLB~;oKO>{h@#V@0W}>dPTtlt zUb>0>g4=p7mst1o3Yji@fKByL+SUu7O}6!t`n1$`vaJ`{rER_VT)(a7h6!`@Lr?g5 zie75gxIVDj(eo6sRC@x>g9QE$vcJ5;_WCD-g|4*su)irc)TG?aVt(h||9(Q->ua$3 z4!75MQ0A&TUlEEKG%be${j2l64C}D<4Z^%(T#nL<(cjW2y~KC-y$rU54l{#clfF>W zrg?~N$V)OYLy0Nwpf}oPt4LTWZCU1hD>P#EY=5l(G$3Ju%-fx)w~c%2Y*A%LY-FaW z?!!`@|6V#B7f&}{`?!raSLpbf2d14LU$4^fX<3H3%Eu?`&>f%H9)}M1CZn_I@>E3m zickqVUlVGxU${#7w@#g}cK)>vUH;t`{~q}Q=4A)HXE?5Z*KX>V0yNS`%%VfitQb@s zO!jGqG)NmOW@}80KC!@E@S{!Rq`u;G{a}qYk+TSODsE4YRaWFlz-^@e?8waPs-;oc z4Xch17JavU(lKCK_qEyTR?Nv}N0FqRut~z4&t&+-H95ye$H3I3;91(29{B<+ z^lILJh*uh}<6}RK*T(wK64l;fmY~_m^NlyZj&?ipaDPkRA_Cf?lFx{&=(`Z)HY5)Y z@1I_TA8+=%P^FY=6OXUZ_wOAc2)e+fFsMvI(sFr|ZfchlNs3Ps?9^v=O^*ybk7@W< zDYwm55h4BE_#{@$){x(4lTofC6!N?>yFb6@(x^C6#r1$RjAhFhea|Vb|v! zt;?dmr09T1o>Mu`{e8bxFOI?Sja$F=SiE&HQOrm`*j@VeV##1!DDzxiHhg0XOZw<@ zr;i6lt;NQJf%v`I8(y&<(#1NIvD-~318Xs~<}}^2Y1M3)NyjxcrT&1WLUV_Qn-qp!FoEG-Lc&acy4s>^Wo8!nnET#TWg}*ZcPT?%ppq{@veq>$pjD_){e9*cQ9h zI@~!|9Qnee%mp{uh%i@83FHVH=3G#Z45Wg-GR1VTWLW^T9n|C|n61I~sPCVmGM64QCy&R36g!x~Z6@pUzf_*`l}cNB?m3F$Te- zKW)rtrG(ok!4q_teZT|sxl_r=oCaR9f;6zr-|9xE25M2(@qyuh*&LH@HW=Cacpmd-w3L*meRe_kURi8Z7K7u$HeZ{08sl_i zmxtfxY?~Jegxd|8y%8d$?os-$F_$Li7%(QpjX&Xx`A*aSa<+u+ag9-5g z?z9mXtkP-IotKJ9Uh9=Y54_~tzV+xI-~YvbTkMJnd)Y{5l{e(_5IkgaMtkI~-k*uV zY}wq-_Wjn=ioJ`)hyS&0F30wLkU>jniUQ9TN9rNx)pgZVrhFmnCI2P;7faXTxepYl zEPh7jOE12oLCE(3j z_FZAM?-NKZ`O*3Kn8TWc^{eVvCO6Ed+&6m{%sSEEc+LOuRR$aw*0+{oQl6vu^W=Y? zG+$eNZ1?Z{-`BA-(w6@l_q}P~_wRe-zJqVTl4$T+$OGMB;gC|aX(NNNczwxHcS4>> zR%uLKcjC}A?N0is*7>?MMbrIrwgt#dIv0%Hxu2jiN(z!KVbpkKtr%fkMaaLy^3p-C zk(v9$M0A94Op^;AWa$m`%ClXk2F|mQYgu0w?&ssb`|*;m-FfHteC^I4<@C{kFX7kt z{zt#WEOdBa{7#0YRKC@MFq6~2E8615%=twk_kHNmz7H)n{_8_udd$pM@p3M20R8!` z4QqTKLs!T*H&)beH$MGl27LUqoGbnQt$y}MrH085_kQae82nR|2VP5}eW$srpr`Td zU;HA`j>aLkBd6cQ67XocP6Mn`yyXH_ ztta7n3HBtM(X+<(BmXZ;%f0n|GxZ1diNVa7#%+K9i;tZPlF=A}%}1^$sB!ZzeKDry z&)|rLiZG;~Z+XT4V9Gfohef-=#LJX$=SzC3ciK;rSlV~=SJqXr3a=lS{r}nf68Jc) zs{Kh@BtUTnM2vtiV1U%Jq=*`1n_|%c0tBoY1auU|04`Bdh*EJ%t0YaU0Sol=Uv*T# z$cLX%D+ENC0zyE9R9vFs=M)zv1TdmN3P}IYbCy|dGo2Q2{iZ+VnRD;C=bU@)x$Ar1 zH|bGi9Mjj66X@rJc}(9RXT0P4(vtvIIU-d|W(p9PU-d=O(1er@aSpSrYPy7tY2IRcxR^5R3qRYoN{+j?pDwpoGE*z-HTmI*m#l zu-OqTzdFxY=LF6=DPN&ZOlF;iiF)vU^Rb0Gy}|0#lX`rEH_4fY*Y8khhHF`ojjDI~ zMipW8E5zTR7`(c1KoFm}5X8b+5ZeqQ3|GE_;Y_CCWOu_aDj0r%8Ghfp%Nv8^3gXbZ z{7L9V94?L)6_|h1Euc2;J^9Mk_pm5>Pg-z=Em8edAB^@Q8vB09FIdlHTHj4Hx1aE8 z52bz0`WIZ!c_+1SON`UeWzs2=w*)%vH-J=+wz#J43+=5fqnHZMe|e3mV}fjQ~` zjsQ8i5pUcE|H(gkNwKS;J(z(#-%kOFrK=l%CU{fh&w=wWNBjcF*DE>d@k0d!@W$(uxJd2rAu*Fdhabv5?3Ly-Ge)#0ABm&^Y-5 z&ckH%bzq|BrpBuUS2tb-#A@J507mQ#03&v+Y{cfVSaEct_lCg6n4>azSyK>07?7ji zC=s#AWh(nvR&YhxTbvx)2AB>x%2#N3CbQv(h}Q70Ji`0f{z&Bv_03bm8H&dS41)Z$ z4CTtP8x}&k_CeKAkk*ogT61K*c!0|a*-&$$gHbDmO%Vd=5;hn;G!G9#=&*TcSUPat z(pBQnWZx=rFdIx9js~NT*kJTgYu~lnKtTG4+UcWYZ;w@b3J&yB@rev?5zhN1U)jb#feKT=pBgbjnlw#bM z^mt$Qe)&_rf<*?rRnKD^ZdDJ9EWjZhb_$ET_j|YX*9%>ob!kZJ5X-scB6^nnj!A}CT@WonlR<)!cehVeAI;v3PMNBYq&G= z5+vr|0EI4Lf0rO8Qo?LE5$C;LIzK~-2no%s=EpJ+vUqY^t0IqW#8~LMS(eaip4+ft zyxEk~RkRuVBGdrAsQ$+J)?3!xum_AfzIQl(-)G?nv@$g$_FJ62VsU=j`MWX2?O+~i zu{CS)(S90mdrR0};=g+O4^EuV*5Qe%Gzizy;=eoQHSVj5P~ii-!gA6L{)LV8{KM4w z=*lNH99GDk$wGC5+{m@aF*2Q1$UT5YYFSZeuD<7UbTtIhI}PfXfxm6oAO_6IPGx@w zw|B6gv8F+Qzt_v%9s-f}Lyt&9osc2kV(pqZgdk$LkpgqO?HQ%l%5Ff8Bob2pmi)nH z7s^W*&Tp*G$6y!5rSN#DIXRmLCR`DGNkX{mP*Gz030eeB*dgtK<`8e9q%?19udLM9 zuu-30Ce74=-S<3jEqa8VzVeabazBb;6(Ak;Ek8d*rQ5hG?V(PL+cK7*Yo~>lnNc5Z zb@BwD(--w!!NV=t=!)s92 z96wPKPvO2C>w$t^4^DD)icE{#(IYM}*NS$`wSsWAiCQU$&pZp_oJ$aH71XsPXP!>d z@IqVi5GAbtN0!lR-FVz7sw7YP5HvXteR`79$ES_&%HhMzmNO*% zJ=TIFqp9mLQzk$4o!29MSp91X1M4`n(76(U35!2CGlDtfo(LGBV-vJVPasoCEbZ1ekwY2y%$qiwWT=-Y|n`^grq#v_* zEOHC;TYK+H-v7{?yN+4BK9P(bbzgG3W%`K^EUPGhbx_0+;Zqxud#=YoU2rhM6&*%> z&)$HJqw;MT+Dr*BB|%mAmt2n5)A2jMZ{3sdwH!o9snwEP0uc|wyH+#(DENa``#3AO!&!BtVusw9-5B=(-xU@+`VN*+VBNuna^t2eyQ|1&> zvAklS%(*K$``8%P3^C7<6B3Ut#9d^p4=PHXj#ggXm`0)(p&#O<1nkq$FIk(@DKTi| zKRP9X-}y_QP1W(_)ooNcP4T830qav!bjec^ z(JX4YI9QTr#GO4YyZtCqO48EKbiSdMi8x_SRwukp~7}=pgSqwnmS8KP|u|+ z|FNmF!2G_?v8g$qWAsBQ-E5$V$%*)H=t;h$5JU2X5nVQR?N&&=GtmIvLfGArcuC=R zy89)Y(PLJy$M9lCew#0Aj~No>2zMYtowgKPl@m@$s7s)r!3J2ydsi0ON z-%nn+BLn0=x`S!?mFww_gXMWE3fZ3)K;W-LF1(Thx&PM&m`dbKboT>kp{|<|A}0t`4zaiYIy|@T~Vh&gl(!E{h*r%0fL&Y2=s9zAQHB zXMb_1^XcD{M&){d{6|OdH{$}aGkIk6b38$@2A36^U(e94C(Mf<)6ZEXd;grg|3M5$ zi=X4Td^c~N_#^&-x~{KM3|55R#SNjV6YI!sOqTeCXZ789@?Ko0q;x&7=yq0E9+FG+ zWD3I5osf}#WEF#hPgNnkZTxjyJX9?9eHMEKi+#_EC0Q(y#ABdlBu8Lfi}-*y z30gK`UGe!7z9)o&$=;dRUvb$CThLHkTC<^8Q7@FyN|kW(HdukZcaE|0v8mKww^>*s>tnS}D(P^l zW9(1ZyOrqOgdc6cicw8=AGE%BlHRqoZE=!)3mY5qpSlRoUJxLd?PNE@2OQ{T5@Bg} ze%$h!wjimt#m?-*Il#Qeec-Eo;i2e~Y}d}?5m3?n>bhQLzovD?#$Rqq3~YxsjxC69 zugccn1Mpqx{)0EM&=d$=J~3Wi9MIZnCcG%OOUQuP8m#u99HT={+Q6m`gyM))q3|4` zs~+B_HrXwrN3p&LW_e(ZPeP%@FiT(<$pZNhlfwvqYfIQ~jCtgXE7)&j_o^m>tp*CZjG)p^$i^s8Ke%3?>Yyp)++$05mzRh3@4cn`z8ce z+k$n49pLJ!kNIuFzuFd&E9{`x(bHGksp$&+(iG%3Nx9-`O;>H6j^{p>A0xsiEkyW% z>Ha``JA6PAp)t0b@VIPjWH}E>L5(acjZlKiD?9&^x<1ZeWzcaE$tZ!c2$CeE`Ge2O?c^{S!SE zt3pnqPvcU-iFae`f|0~}VBx;E+?SQ}6***+6@V?m;RjAEkb+LikrqnYWgg@tk%Wyf zQaC^r5BH)=ekBcuQ5tOX{>dZw88;^>NMla>Abg-^vxa;6Eha$N% zG0Gnmy}XdP2NFk166a*o>v;uj>jf{9eMt0r@I&6bvDwh4LLX3}1=g z6gl2R4pbEb8WcVNg(po@D~ddOaO4wrB%vocEs{f^%ERx=B=9hsZ|!y2YHXD_1_h$m zw>BOnl3{DxG`R0D*xQ0?MIxx|VSbd9f9ZMbGuTO03YDIx!!SZgx03mX zSLlkTxiB*oOODOtJh=FF9$xWE0$R2fKehP5G+vCKYcBn#$Il+Nzppg@ApVa!b4|AX z%o{MkU_>YmL`R*8{&SR!Mr+p8Gp{%v?c;f)XO`v_Mu`QxjQuxy#F;KVdVN+#r;|B4 zc_LIkP*7PY+O?qSF!k1wA*io4IZei@DedTLDrKDqR;_AI^j03Wu3>kEopG%vWJj;R zOUK1Nj9!$&D*amwVte;*A_(u~gs+3xCdV&KeKs@r{3nkQxco)K;*=3v8(pMwk7GNB z1UDURBEl86cf3y66IIQE4{7G@Y zSbilCQig*(5aq(I!By4NojfhCr6Q}g1jPTd#v4A3}suH!MCyagA~kd9)CM_CCYwH+*uY>ua;95XXbfu6IV~DMKdeaLf<9m z#bk2%68X3;rM6Ldf}-#XI}Y~8B#(k7#2nn$5yX8RAYNJeEg*u|M3Tuh56(#T&Da5@ zcBtnrnSIbtemG_{bMrkc-1>%y?Yak2{Gv*HABq)+UaxZL-u>%)g zoDvKnaj6{|;aEa;;ZT`Da*rNi<8ca4vhg_O*SHD7N*(p5)$RF}X+*(KRe(=%K z9wL|o;g~or)=V5g;i3et@86xjf6l}P9A)fbr&3_M@pb#+c$n>#zfLoE9^cV@gOTd4(HuDn0^Mx193{aCdZZ+SFxt&B zJ7OJnpK|yzA8w8yL3Zx=U60@VhXV^H)5A=Xb1}$;4HFcPWW^(>X)c!Udq>_efg_* z_;s{%&yhL9Fk}=djFKD>|D(y01lyRj`6mAa0fo?^IXE@K)j#|&I-8$?_vL!mEM2lq z61Fd0w#_lD!m>s_2L$WSs0fPbQ`*j=dOA|@ZWc-I-C_L3|7a?c=|(1PzN!1dRP}Cx zPiFXF83Jdk?xeT(;@lod%F)qlFz|AJ!uqib?AOX|O7-AlF^ zCQX~%ADvVPe{=&mu)!8df-Owid|TveU+RzP@W)3`7^Vr7`hy9j{%8m7{z&0B{zn^_ zOn)$G^KIZTS*-rnYjQi5Rexq#^$YHxN$!p$^}!u&K#U|bMUr3&lQ!EF&V(G3;E)&# zHAvwljd37k2?^OGky~%MG3H_rBnd8(EKH{$bh*Z4XICt~Q?Q*fFsXJi zv4ay0{$mHe$d%~>Z;s8vlFANQQ?#?47N@AMZ1%y3P1+B!u_lEKIy&95pC4>*$MP*G z-oXUWD{(gBm|Cx)YDsa6ND0~m1ME+Flea4+;iwE{3rz((~&OgUw=d2!x77YA?TP;L{5hpP|7!j@v#*{fmORa4oCf>5viP2= z{rc*ocj2BKibnSc+k5KvL&Qc^SAP(%Q~Jy7gWC!Q7T+%|kKao6CC`{z-)7mDokeUZnLkupaRu6sXp1KhRwo@%JK^#QReU3 ze8Bdu@V>L%ms)O9KB6R+1TlyfW5}R6~w3O{|oJ1E#wH*;=nB6ply=8P28+g=;Z z^vF+3zFa^ny@iIDMQd{%0cnU!Su7M+424r_+%!Rea(j+QL~QWQX?x#(k6rbB`vlKl z5}es&hjN^9;vBopAF#KSBu(+AeSOKB_I$_;P1xMeXdK^$KNI|!?8EOIZ%lu~F z;%qmo$teXN`;>2e#@gc1SbK9cmP$lp9c}P!iY(k@MrU0subM??T`4cwMQ5#&*WRMD zaG{DP8MAP%CWw7Hx7oP8ngHTE0x4+7FQ#fs%kTW|i4F2+Wmy0(1xmq~35uEEm??^> zV%kkZXi&2#x>nwjr$KU*0)u!uK!YYwc^Z@xT%zxPKzS!!rF(q>Qv6 zP7DQC$stA*)g2W2S~=&Tzx$;+S2m^vSIHk=V)ar&V|OdKM89v~&B{;Jc^H`i__kb= zwhI3M+8|3cnX{YB*-hr`rj4DQqE)b_P4G%NSY+!r3%x@2!*q5`XfzVAcnpx^iiql^ zI30`M39{0$_)T`2CB}M#0cvw0&Hgm>j=3*=;^qracPhU~& zi0owg`8E9~Dq+~w! z`2vIePM8XCvLVOFxS5BMU#?GKraYLcIQcg52jg{T9WL;A`A<)mi)z+t>5sM{_(ZkZ^`%W`9ckRmdbBUfw zJTQ6=4~(7zZaC~fsTvO_>^#8llcPF1p|T>hD=zUxBjG4)9k5hWuh`4FB`IC_5EVTXsj+8J0=#5ofMDG!lUu%EUb3Mlv*LawQBx6fvCbsQsw_eFfkxC;wQEwIS3`=VBTeS4$C?f(AgFhueF$!1>S zZo!WP)={|DCr{+$C3(zl(yrnK+#}R#Xb+jkhi>?`5scu93V)?r-{Eh?Xp==LJow^^ z(@5pO$UD>nf;K>6v=25nh|HG! zN@i?(BrxTcTu-lKtVm$z-qU9514n{3b}rpD;PG zp}ha$`6>HEYcIkKmSc5WSoUHSjPPabGlp#eEKAxh?%!l5#}-1dHXl9`jNujtjId-q zA3!If%fD`PLc<(6&HROsp>*&x`RT&xOoG<=%xoQ*L{N>C_w(X*WsbhQ!Bu`+tXpHiUul9@0N9RwatYBEQ9{ma${_XxXB-F#gE*5i2=IHU_Mx?hed35(gMb z3fo+M%$67a;hTi`hJjDxpgdo9gN(F()1{fcuQ}pZV-9NV!<`5Oda;t|tJr)<56C~7 zQct6l*0Op07Oka-RxfLJw!1d-H}+bR0`ZAfZ1VG~9;DlByV9V_<;giRRplp4Wr<_I zh=IM^&VTg0p|y8rBfB@^i9SNG*yxR*V$1SB3^e!v73-o0sT0g?+A;VLM zz8ToB%)|kZcVoMQUW|rUX}zRHWUI!;fY7G=UF%pSQweR-UPXBfe;#9`iA8y5Nnw_Q z7tsDN7e+#8{^KG~43{d1WS1vX2;br=N~&zxfslmOTBL7y`S{p_X=RhXlRCh6^I1c_ zLxMqVNNRyaw4e`ucqU33qZbD~{^M_s9Q>E;y*F8RM{@0BiRUnhB{pPj z!2prtFpc6g*A9rfZelT3Y=zP3gHq@CfXao2ly>Y?1s&bmNiYL>Pw6lnsWKf4Oh?Lf zq{auPlKkd^^##eOW9!;Uc0z8RXnwLxv@ei4d^GA9|2oY#lWhc=HvT5ITUi{5)$BGL zx*Psihwh?<6jBsA_7{7*Eyj;rAboNxaXA){@5WO3-FUsTk9M)|vLssU*5quNuXbu= zq;gudtA%J4bnZd}M34k33tG$t&1Ej&tM5VGY)I0&9rhco z8z{Yc2%kcQQ*8yOqZQ|^nJkML?nLQydGK2>d4XK6gRgV~V7ZOu`7NLz3hPZ`q`VG( znbn6-Zm+aYa!O+pEqI{NL{qE9oYSV?t}C|6k{zJNmxa<*Qr=aH!8S#`C+L6FJ5Mau zyBNo2!uZ!Uj>|=Fsuzx$yE@LiFyG&&*SYaVHy4eRR^V)kj4!Y?@+2Nf?h~ zOby~iMhObpa94hNk)Z@jchC?^shf%k8d z&y&i>Nk>6)8IBb+t->z|pU=Upi|=RIm*OGRs)W%CvC2FwF4LkK2OJC>Og|EmFi8U{ zcn?t`T@Nxi{jeGNPwRt~EZaA_{2N#XafH^^*7FY1IkG~uw3V2HA|wi5M&uEUx)w7* zd-3o{rcVgdZ>*|lge_|*0b**bSv2WqJ_;G!$>qd|3o4+o1 z9MbY9_RU*2aU#+t<veRZOnjN&Cczc}H48~_I) za2${E*sXX3$2R4m6~}5GpvVA=DNrI4@)O2CX#82@=jbB`E^M)VRvO#i+)bE9m1z}(htL=(a>3z>qY&AUL{9_{4C9Y(0c!g zTd|eGhkJ&C7(N6sl@noKMrfYaB~ zxlI^_8T{xNJ~>wUe8%@vSSYb$$!Y;>U#`{Av9MOd>Ylwc&WOeIBuhwUAa zeXtNCU6*~vn0=Zt%2;OLu`g>(Yml;$G^RCplgoFy^!UD=)*jz?+FE=>_q4UKJ~k#^^6{Ut3&8j+83d>U1;sAv?nR{ ztkyn^{1&0@My>a0*v}J8b{zJ7cANweV+_wvIcCU-Dfp?M^NJjmf}i@xPdmsjN2%bC znIp46jxnS;#_0EJRtX=+m?lZTN#t>iQ9Ud0z>5vATJ&>_X%{}VlYST?4ZjYO4}KUB z8jm9c;eSE+Ul0unl+Gf!qWq8<_K{Cdgk_BB1j%Zpi6vtU)nXiq(^XVK9;z-KV;X(? z!FVmkN?|U{H%{tgKV@(suGoQJK!`MiJr=|qLgXYNbYU7|GOI=Q(h#9q4-u*$4^@|hNOZ|DNGOJhub()EOY3v#l>gfk z3=5p~ZXoy|TW&5!T_l3(Ur@Q*MZVKPKM`qTKc#y?1Ygh{)&467*643id}kz97(93E}s-sI~gWzl#6;Lfee#`(5vL32D?kM&Oo5M6H7)Cs`W6T3i42ONtkq19Z7!m7dsRhM$i=&7=9jE zod25eLEz1p|0MpnaVP{~>H1xV$kj6Lv7vQH#grxiZ$m29>>NA!kL&ji|6TF;D83`@E=>G>6%pr(lw zH6w;{oNq9I&_QUPHr9cc_+)K{;}_EKP(td4l>rf-CLwKG>*$hKF)ASeo)5N1!oGZF zFBUF$Vc~KY7cO^k;c^!jE_cP*rKz&o0;YvL=x)takM7nav@HnG-5P{mrV9p}9XRYn zgdd-ilYXN^Ds{#reOY6+o_z=_IO|K~=dd%#^jX+!0m)DObWgwWQy=>t?I3^J_&doD z{c(03bZgQacDk7kBp-*J9zi@pk#r1qg4pB;Vu2$07~TZcP7XT_(j`|n#(?xgz0kOm zA&7dCA9hX%Vrv1UA98}2jRY}#5@8?tN`@UKvsz>?9d@YJhaIXQ4^@{AJN6k(X>r@k z@b}mqa-=rw@mO#zjWq z!>ezC*jNf;Lrp|%kdLtu6UShM2bvBdKVu`pbi~rc*2+))}8Fgc94IB>EO?j z9}eq4dBzfH#?mm;f#hQ>jSAvVJV-j)UJ$V$h&~|Yv0@TbI~hwU((pq&kbYzR(=As>UMNf0|JNylPB5IZSB?4%?gbC;mn$>13zjd~4`4}Qo9y{d7gAa+KAnC=BJ z-2>@|ZbA4-5cMY_c*s{0JWOV_$X*&eRO`V*7387nlHlpG;9+y>ET*&7*bi+W%EMKK zVW;rK0H=EMxKO}^URb`sE@4JHsWhBRfAL{G zU9Ekg+F`;b`am;;vF)3HZ2P#-h%G@~+j0P{7aI57gb!OPtf6_z!8J8wK2L;i$wzt+ zbzmQ0IvbY!jOh&1(Fgjaj);BZPaA&>H2G-<`8$n2MSk>wdbn1$ilAky$aM4p^05!# z>!?7i=p-F|Ob~k&LF`>5A6rF1wUd1yLb>q6B>CWn3FZUyg4ikwV$ULots;jdE{ zLAXi~b25;w91z5uEQmRo2%Y3(NJYdydRR<9k)I(lu@w>9N+9`V+Xwy;rgKgvANXkp z`Lo79NPf7f9da2`q-9&lbc7W77*hPu7(LLA8@1)ke9fR}x4+ zB!tGcQV`opL99CkvCah24-JCodV=UuMA%2Zl8|CDt3~$GkfK@-DXJh3RhNcT6Gu2{ zaM>Ez@68UC6o;}ZtGCUGlzy}bJ=6oFhtfi00v5!?B8Z7a5EC$v9%>iF=qG5;mC^F# zW2Cg9C1e5y&FYh%EuUaIBBdF~mZX02$Bm!*$WJ@SA2a?4`4K4tKt>8_MoK-?f#hSP zGzi`#@+e>RU^OLtID(aYEDQwIPDV$PnHW*n;BHN$IEfZ zp7;i{=aLCLaQBZn%MJuvRO%^gQ7bPEU$ikSfe2uWv`B=k(g$K_nI=iM2WE(z2BC4- zEqtrxq?6NPL}*NVGEXZXBQ1lvF*2D>ch`fDk=D<2L}nVu$f17ncN%}3eBf_3{*>{z zkspyc31noFW@NTA9Y{V#W``iIPfI!?To6k&K|FYqe2lGvY9}Lef;9Y)Cm-sc6M9wS zs31OJB8YiS5MwQnkvSxYF;x&_H4*lauOu>=%xaOnG%~5yBaGkuK` zSsLoTV#?t8`=4)2pl!uxYOmyG)zkIHo*)3iZes7lZ|XzWfw955KYQi8y47+_)-8lp0ijseH=dx*$a z#9ogyzSV;Rp~7i$$@>fbyn4oe*!|zd1JB9kuF0QcH;w-X-v9m0p=@kk@{wg9e!Rf@ zzpY=V+ZO80(uS$-|DJ6sOC0;JegF64SF=j3dgZu*-m2G&e0md8`U(15n*F4(XYZUiW0cO454(vGLF2ige*-@GntSGLKT^ zIXFfkGhgwAm8@uKVc!EEAQY71$gf> z!JStpwa?P4dwes#O=#Wy%Q+PdfY==5x!Co;Qn4ANRS+wO1rQq(ae5fRjp780%+l*y z6kE^wF2@EA>1y+Cv>7>BQH)3QK+TzEwbAC+hwS9cK!%(4E||8_fh#^XkfR;=*uVs( z!Xx?&0d}D5=6r~utwhJuzhw0hHn5qpXb*L=F5Kxg2;#6*Q19??)0za$M(k%ge3KT` zXMFTcN@(1qCLh9;d~#4>`|#dNxXhp?a!`|$K#g+i@v5NlC0F z_M-`w%~;t%L39c$J3`#y6nusO7Yj=+D<8{sj8--1IS{O|T#U#3yMDs%Z$U02t^OX{ zws2*+pG{EOo50TAlWa{%7mI>3!wnno(CQ4-!5Tz2N)GVs34wZ2;OA3Zt%1NQIS07ag2+%ChHz2XOY`=+33s&gwuy%MWR03mR8~Aj@i_vD} z@MN4h?7N!tq^T7b!)1eXJvdW99}7mqgW1%*=z2*X)tP4k|Q9*rBMV*tP(;g@xq}ohp!gMy9&bS~#0>}}J zd^Q%2u&s+@Avvf?N}zTk0h&bO6k}mVB5?Il6U4c(AkK9K?YSw$uH=Cy0+ix4K;K5;$Z4}2)<<&= zHMJhP@}vly%?J%Q?TVjh=(4m-bb{zQf|%$i6>ZT^MCkHq6zxn2B3uN6r)|)Y1HbEd zEp%`i4?~xAq0Rw8TtODZvtuBq@lhf|H*Pv(rZZwXC&)hwqo(kqDUfcVJH*BC89o2JopVtejXQd?{Xo^l}PEPMlK@a#Dqyg{F3tHAUzp z+05XK0Nu3F`!sx6nvK*fs8=O83bs)y!Z$^Pj06J#;oB^zk0rPwj0p|51F2&W{H~)5 zI;IL=)`dE|1#vVbh?`VEhVK9o;XA=}c5Oj)Z9#ivgYcy;2>~L%Qsf8sg;gjys3|LG z5>PvlI7RrfmL9&W5z9&q5&ChV%>x^UZ`bCJybs)IN0eN};QNPI&kV$LJ;O%i`l18K z`g97~Di-^{w9t67f#|y|G?Vb5H%K{j5#htgA$-_B#^eSKp1liVauY<*ua)QAv z_3f198DS+$?z0b*Mo49e;1e28?MN3c z3w#YjFEb@x&cS&FnTwnHBpdA6U4YCXxD~d?GV1_!_hL1bR9=epm5g1a%h0& z9BgVOl;Ob)Iw?8ZfNom<-8zo4G)IfHAgU#ZYEmj3J4l3#ew1bP(}Gwo2x6ZshOW8OA@url zz>^I~)Ef-^#p|tEV8w_YAyYA;n^{3bw3M}oZebdHKY|5{`+jg(BbxjbLN7+OXwrxv zRwP{C%tC(^#EkFDvVz#G6xTfvkG(QN+ufsCOKiu5!S1CLk6{D(8n+86 z910VbLjyGDy{6V9x`Pn{&N$F(eDj?e(Jak2Zxh7eDu_Fzl!}N>6VVPSlx6kX1QA7o z=y`(Zc|h?h_+7_4p<}9uW?iUrr667o5yU%eKt}X15fL2|*>JfaTuwf^To5i7eq0Wu zZe1B9*to@rCI>Z13C!*y0TC?{r-x@pwBPNQP_u~UZ;9wl zMDR7im>fiK11kidFzR_WUb4iEGYieyjD~lYKlDQlWEQIsah+giOQr-jZ;R4zVyUu% z*tm&}nb?S+?I{q*Ia@lmZ%?aN3$5Y$T?$7%VwS`DXwH9`S`XwFHaIvZ!09!9;&u&W zmS%Is1+l~y#F87xKyD)25j=u8+Y!XthlGhGIGEV7YbnZr9 zGqP=1;c;(t$yY>_t(PFOljX2}9-$`uOP+(1Xm-rv0U&p+K|hVK6e22(QXWx9-m3kS z>=imx7VHd~K|#z6g2DAjh;1$rn_r`1sj`CDP7|9lv27+cA&C9~q_s^miFoV*T6^Pm zv>7?M?KCG{$oaFW^?q8~%5u461abw6cYsIx^7F98YSW+;%M(m_N&9b`ImW(2Xk6I|Aq z7DNC78P4Rhep=7QE%sA#P?MBEUlR$aok*ObpR$(TPqS8bM9^xCvJ<2??xzb*sodi% zRAH)iCOlI;{s!%FnxHR5$Zz<3gl%qfmi$Iq4=V=WXbS3F=ix&hmOLfPmq?A7cAU#70akRaOu?!ovWFl|uosc1j*C2>nrj&uUY#}1f z(@clXPC+dC1hEJd#4!+%aZWyq^9eR?G0w?BO;Q4yL;_+}Bu)|MBO*x`R{d5sBWN{7 z*(~WP;#^+P&@j~5NN1BxgzY)#JUT4<^Xg%me@=P4jxdd$MfU<+&7&oV4X6 zP(pJ~F|{7A1B^LvrhsnR_r9<3%F=AnenCt(f|!0N74e!SB3?6$F~m;4AYw!i1CAh; z06^-90bR#Q&@olKvM$s)Du{#dIT8&Y*pY#;*x`}bw;<98|&42H^r7NK- z0gjhNI6fC%7Aaf#EU8D#`^zG-f5vW^eWkElF57FA7*Rg94^GinhODn-N`m@=8?>*G zzd}&wz|LS95yWgHh>2DZ!31Qmv;jR>8ci%!RuJ1S#c{YGSouNb%6f`lUGgCFEfh|L zmO}%yuk3GXy{~jL2Eo}3bkll=w6Dl(-H0Z>^38F!QxNu0D*8%45q+hT@r>%H1QDNt zII0oES_?=WIqK)j2@Df&vMNYcK-8nJ9b&}xjbDbiE)6*~qBM|at4 z=%#tfK(2C-xsnw+r+l=}Fb%$rl!V$1*J%Wkze1=bEYzAU)Z&6Tn-Ih@6-cpzST1=J zzSqQ3Wd*SlT%aQ|a)MYL2x7G$SbUzz_r)2(6t8jmX$ptJxaH6Q%{j-^dIa-(2U268 zn|9~-G=f>0?ZmgU8Nmrb9Bxo5A~;1v1Sc2^u(MeZQ6q@z3!?f!sXq8!#~IKuRRov4 z`wpEwrjxJMGlB<1C*qFj^o<~VBM9FJ!Z$#U_T;k&9$@1ZBbXelos@tkk$@-^iBm){ zYw4?S)`(@LhA1mFMp^N|#u4nhWeL-Y2bn$)Z{BWMWR(oh`Afd3b3~|wEwxeC;cl5P z(Nx}z%a+yU<5il2M7%Ba^;H_#8DKP-;4%tvkQcrv+X3^-cv??zvV09 zm2=YgT6avoMVLl&>GufVHdnr=8kgb=@#;Um^w`cX1ElP$OH9KZC8XgaF~X(W^wz#> z?S;b+HYVrxeL72tOPAj+@Vy@bj$ZodI|P>B2{^v*vSEQQat(G&-xo)P`d&`p2YG?3 zCIr4Y39!GI5`j(*o6M2n(~weHd#{y;_N~2F%4G|_=*6OZ(Tn_i(Tjrl?tD%Vr}w~l zJ@`}6g7`blz(mjL#-9O^{8IqjN9i}e8UM+j_w1iH;7gq^lfMv7H}OrR)8*5dMGxIP zkF3WDYjnv?bW`8L#uVCzf|jb-r7J{9V(Ds8I&Ufd)HQPXOo~SF8B|VCjpkc#tOVbB zlS>U11Qk;LZqJw|5 z0(T&j_~2^f<~{ItCgx=4_GWL~6Q%Ky_59MIKW~_#UFC0pl{a;Cj|B8C^gmq*eUqhs zq{aV0EfTv^+sYqv!MCHI!k>nD3it%@iQDA)vnX!e9x_f!@?MlRuYTFUaHJ8@Uj%dyO1v4?jA|jP3YSl zU6lE_zu=)Qg`IGjxCQ(RGy_Pw%|6FksJ(ck_wMBM`H(8#X8mSbKU>)%4j6lyA6B=* z`uA^(-BJR&iWqzu@@6xq#zGOz`=C z#rqA*_(D*Qg=!GYZzaBLCX$SdreTd^rx&tjU0_2cNnlBRb z4mO8W@^MIQ5yatxq+^RKxI(|Dg#47`V(5e6W_V_0aN5%~E^PCun@aImw}JsCl)Qv^|e zA_95P=&Uq%Fyo-v2c`Lo>mcgqFx7AT)JI>@4u(nE_&b3NlLq*ZVM3Z=(#>=r`4}cW zf;eH6bX$Lce?Vy67jQrr54%8WI9VYd{4gPi`*ecX6$0spQ9<30 zupYytp^tneVZvlqi|nOgLbV`4 z;ZHH&;eJ~llSS#*9JCke`jWTfb8J~eSzZuFq(n6T1Sc6Z=A_X%LG3k+K9PgoAiWC_ z!tx9omgh9sDPeb$e5{UgI)v6ilzQ8Rwm}y8Nzy2<^6Z6};!K!eHfBO%aVESCkEJ;z zXd%rFC&O=>V67MqAXpKNVi8()V|9?3h-=B=Rj%Ug)gfkKlg~cn+v=HD>tO|pRX9~> zroprDWLFT+IR&i?&Vs+#X&^RJBKCV%saUG4AeMsz#f}K#6i+Z1{2-QJq2OT!#p{v8 zD1{?u&~j*i=G<&*IWV%xatr}*_5;1fyMTui4oh<+$qFJI1hJ>0RE7Z&GI%sfJF|k= z4hUk<6vW^Sq>cp8bzFC)9#%}!4#=WC)XBO~XFbbeYtbNxd7Ls31uaAjkZGnvXQv>Z z1_&b51Q8`bhA{c2K5ioiHAx9*5((JEh{P!lD@H_;JxZ~%89}QtH$bGPIINH`MB`1GX5f879N6ZD?F~>$|_EQNfC=I}M-XdlAjJ-|<_K2O8j`aq964#rp#hq+gQ@jkm8;9( zOd&|zv=4pNo};kOp*i~nb%Dh_=^&*dz_LUHYlZ=a!08u6fCyrtCy1pzkk-V2u45bM z;0zfCE9*j?oK_jEc|pwf)P-QJrz`|(o5;pf4?(QV1#xC9h=Ua%gOz+!pRAFCnxq6Y zi3HS6Bu){m8IfeqQLJpIpw$><`$=yctg;#7jDpF@rv{rk;DRYeSP57Q&-}Z-phspB z>~J?T$LgP!b%F@xes~CBgk3eh-y$15e2xMWTqC&ocQEO&0piMJ@Ae!1IvojHMV^@M zf8H|2-9bO&5SSLkhE4vuCI4f8q;&a0?m`zf*5@Dn9ovq-e#b~@num?+!k{Syc`?Y{9OD+%bqp%cg54|BlzQWlX=`Sh~O_=9^l_gpW^S)<{L!VyvBO` z_16b7f8t-nEb=dUMEVD+oI=2+z?yIWGP@KK@{c`;U%d}`#*xhEeJFo1lB2O1jXWvA zUwNI6dxWvmaOE!cKHRs9eUNUv*3Oo$Y|KEUbQwVekRT3V#Otc(j{k+=MYq$E+ai&_ zM0bB0^{B^RS*!y+L)I3o0K+OX+57azR^FYl5H4oH0Q1d`l57b4tl`j7K z=*|ftx(t^ZW(==0yvOiC!%4%JzCz148otr6&G1aag@%_J4jA5S_*27&4I>RIcW1-> z439EwH~g^S62mVT4jSHJ_<-T#hV`#h`MVn)Y$mIBfX8hJQ8O zb~lwDH+;QetKoYMyA3~Uc%|Wuh9ic5G+ zD)%eaK7Q$hH1mg4X-u4)o|4CF~b=Ls@z=+n+@M= zc$(qI40{Z}WVqD{T7IVC-iAjQo@}`6Y~?%j9L1#JV#6akHT`Tu=lA^Pr!@cThTk{L z8UAWg^W*Cko!|3gd(VyVU!!42rB@n0X8Mmf#Q0sV;qMLW57zu2oTu1*zT#uuimx^N;K!7{-|&s+D*YwH z%R7}`X?TO-eTG;6Q1kCIOx&&X>>n%McJEanpZ~)z9(afL`?SH_14=x%iq^eLr1o-rhtbt=bIa{!@^&^n{oe%lGCMaH|6cXlVC@v*mg@8N_HrTZ>5ffS ze@9RMS4Xecz;zneSKhpN_}{(rWInBgyZ%V!c>dlyD*xyVIRA0677w%lHzZ(CGoxjs_THlFgwWHz9iYL5N@y7Qk9{nN3d-qqY zX2)~G<$nC|jpcNu^7oyu^1t|u@;hF3mZqO^zhdSO|2Of?ztegz%~^8BzJ>TcR}e#l>=r}o#cexd0nJy4FP|4He|r#26r-?3V`kT0aGmH%hw@BX;@ z#qpnQPpJRNCpNF$!gBwq^r}bHA47jE$4~8jw632FKfaIBA^yVhPp*(3vGiGnpZkEy zIjB?dp@8;u$8$Q$`8~ZFe!q&`YUMru$Uy(gjDFR5YS&g5y|~zZ{);Q`#kQ}Kyx1qz z?#_=Zo_C?*GoMiW{soHXrxoWK->H^&ggrs+z(BDC3~K4`f;Xj zw&7borS@EEcKz1q?JR%X>i2y0{i~L{!0NZm;<3-@T@BstFI#&!{hxyW&+7T1McV%V zZS-1;gHsl3`dNk{eX6BzF1nm8#Zdm1mj8F>uT+0ef3fNp%Bx1dSb9S_q24h6`O+>o zM3?93PXE0z)aP`Fulcd|!^`j8JiNFH|L?2hA7Oeve81{@{HL3TYpd`dTt&_b%b)y( z>f3#E^Ke=fzL{0>ceea}A5?wsf1dcdbyJtWCpUHa^VRdt->STwP2XciSCdzbZ!EC? z$_jE_ZzX*(E9ZDoQ2!RAFZ+YmPhj8qM!)Pawfhf`Dqd$e*V1?Wv!?&2;=fA$-S3X`*Q?(zd6DoH zljZWR2mrd80^BR0!~AOWqpvKN z`%FN4`DWAen{$=_p)SSQ3lvW?{F32)hLQ6$|2pGeZRNl7HBCRt^3T3N>7`#){K!(p zBQ8=LTCVugWr{bLob$e{^p7%%M_TyS1P{ubBgab`jD%X-u`OEtxf-VUsw9Z zdllpNy*T*O7g^q8FS5M)7pb1^FPFD&z54IhPb>cF8O5ibRs7`!#aBM3c(LK5hC`9J zNZkL@@GpjuI!$jd+}rRV!($EKVfa47j~ezFUS&9JIBqy|n#$kDFkyIx;S$5E4DT=; zGn_eH%O7ZXjNuuEOANnc_}!@TU-k;c6B-ozc2#`OZi?T0mE!MSsrZcX-DGqnyePZB=P15;rQ$B% zQatdxiYH#L_>u1`{`DBebzf8bwaLvHeyB^+W9KWbxm)q)hDZKL>1PAl(;fFPz9;{v z{GZJ$t~R{aaGP;WztGV6J^#t4G=HmS6c_zXamet5=}GCgKR0xK&p+d1nt#r@il6UR zTzsD5|NFS&1q&4W&R6`}C;l;bZkOt5?^8@IQ|!4!vHNq1{g*0sepWHvuekE_{}}98 zu6i!{k>a-ZmSfvJN)H+C{$r(o_NHT`eLggA^YED}{CifB^A^khe5>l4fBfd*O#%Od z0bO53&H8CW5<2gH*(_#MJ zuh9HY8J>6hiDK7afJ*{8%&$hD7?eLfpuPO1mOlK9+VN|{#%Gm&ETBEzF=Bj9Zz;aa z%IDsClK3J1HpQ%AdcM+;_b6rz6Yo_z@lD0tQlmd$e5YBu@ukmDI{rSztYPZ?N=MFA z%oxVssdV~Xit%?VW(`v*rE{h)-C=xZ8J}V5Y^5XTC}s>3A5=PLnEsH`@eeCz4O1Uc zI?}0_F-&|^>6~HuV@k)*Rm>Wux|EJAP|O%6&Qm&Pm_A?Wc(-EW0?Rjye^TkJVdPUv zXABc*<2Ot%QaXN-l{1Wd!pa$@7AqZDqL}-%V)`?R@rxC+hN&K^J9FQleJ%+p_nyHy-(@H z5sJB!71M83OdV$F?@-Jhs+c)dG5rCfPqO^^iV2gGJ;nGgP>e5BjClFkmTr8Rw_E<< zin%k5Hu>phr4yE(Il%I*T+Zqd@6hyYLNR0VvX-8Dr>4g(KQ-5Avp;>L(vhPS)9psv zc#=3!>D1dS{r!pw_mkB#^-0UWP%*pC+U;XXr$1`xixg8ADMl76=02sEPAevijvIf* z_!3Ku-!T1YrL#7ErA}47=^vQ>n=MYvuH5NLCk*4CvGRtgi!dR!^CHm&KagZr*z_HrpGY- zbF=3@v&S&N-E zF#TPnK1DHM?UVbFrQf3%|FL4$Fmx?kzYPZcwU@n0C9VQSR)erbG$iC-z5Gfe;5@_(q9%_ye6q8RzQ_ZvW(*T+l+GEZZ&f;en_|{5b-U6Lvp;c% zl`~A=Y2}8koMGxND|feI#xP;+mH4fur*n#N8$VJHTDjj@Im5*7mChNa|6t|*sF-@Y zV&ol)SxAm@!N|rgY9Q{U@d46N*{G)Ss1( zJg%5AO#DUZoMHN}O2?m2%o?ViR66pMV#Y8rsdUaTyj~kCjO>$ z&M^I)((w(7S;JJs#-%zNmkblrY+N$T+B}@v*7&zGe#68}<2OuiuXOyS#=nd48zy!& zej9JnUpD)!|Kuz`y_?B-m15Q~wY$=hJrpyBiMY}^!}Oj;+q@UwOX;j(>eWg|8Wl5! ziM_3yVR|2>GPD1pRbrTOm$oO0~YUw zsoy9a`CrA1Va~^^=~ixr$umrCrF7&aiW$SK#dj)d`7z5kOzfa^&M>jH(T3@IrPDhq z#$TqGHB9ZKbmZlV8NfK65EKVXHS2|;u_=M6q!;H-%30rr>8%&<#yKOu=OVRhczR#_+zpb{f4fnIY zZ}t7E?@Pn|sP8j{oEnp`R^(( zmpM&oN0%3#BlvlOr@Q_TJ-@KuH#^-PM=$65bKehpx})kIk)9Khv=wD0?U zpYMDwuM+w>Kzy0{E!69@%X7W1&(ZV!9Ldr3dO1IrD9G(qz0Mb+^A+%O%J1g}j(%R? z=L4>%61sh1zNZ(^+S~Q}`HiEehv@BF3H{u`_dC9i@qLT0(^IxCFRa6Fw)nPnwy&>! zJ?-mfUuXKd($|r`ZuE7cuM2%0=<7aT&-wbz*J~MDpB2`D+t@f~>o8w;MQojw`;x_# zt*3nbiQ7KF*DVoSpZK~Y;_DBa4>G>au=Rtl8+@JM z>jGZ~_`L7?IN!JVzS#F`8QYg-ZGKPN{O$9#&(9g3kA2>?`PS!GpHF@M%-MVyw|UX$ zL!bXLKHu4XCuj3l*5<3Y&rde*Wo)10`ypSq_MEPxo;xV&h86&KLX~!R7h6kC*fGh@FFYKX?B-?efBT*8S_}Cw{)=?NN|#?OsT~ zPyOhAP6X#)|E}X^#Or0~e)M#2H>X4Yc>8(2x0mx3Xp`^qT#xSyo%Z%B`1xz*cR#;! z`m37m?c;h}zGJ}-Kj*S@QjaI+bNw#IY0r0lm*;U*DL+hi+RHobazlMid%p8~d0#Ji zen>mNuV37+&hP!q`COmZ-}QL9mkaqrI^=UXVY-_m^7=NQ`Q^4yL}d|oc(_j-7~`_0kMJ43n7@9CjjPj~rF zJ71-FwNkp5_i@7YyF90z&uP#1^bjlYdAg&QbK2v*p`Z~G>>OoRWN_nNoL&l#5Ry=moi@@e(UbjxqA(|o75KB%1kk*$^9%J?7K zpyi$Z#iY{HEdT8$e`}+!HF_JPSIsP!f1TyO#PWX-@c%N%&l$a~@rU%;&!|0~e}mEP zk6x>f>%0BcYLDCdu<_TM{A103mE^tYS=HzDdoJ)-=>HuKFZbsKM!P+sy^Cj*^H5` z^565E>ht(~t>t@r9%l6R-o8e=eP;&ps@3;g<9B;MY4mm`Kdj#h%dfNikhbIk++qA~ z|9wV#{eKnYhxHHr^E=~r|34han{NK|@!(}gl(+xZmhbko8twi6#DG58Xt(!tqupN% zjP~}u$Y^h${viL0M(Md`{So>%lo!?~oWH~REHV3Ce~;1bpXEk-{Vxydn+fv2 zVYK__TSj~PUT<{N`q!O7`4OXcviv_7y`#}#eP6m)xqrg-z&otG*Jlq~zr57s9bmNQ zhwGW%?Nq+E?`w_U`}Yw>yMK1yyF6Y@O+^Un6UIZe{4I^Iuz!d0uZ@@cbC*5K>2Q8} zxvh5!^*7r4_iK#y@j28N*7qpm_x29gUvIN~kFRh&7TR~J@q7H8VYIhrXipgb;rJK& zd*GGoAD2HE$iFe5Z#UY<%TRwvzX5-%ng1%;Tdh6orfYehZ$tal5k^<%jJRw*QvKPgVXy%EyaP|HDCjLVLsdhx)?$hvh^5Q2zVUemI_8VeQ*%}D+Bw(`SgpH z@AbXfXtyu)=Zyh>HTl)#h4s0|k74=IIU4`I zA6_4n5B>4CApfTN^75hla6J*`SDP=Z`SUefm5&z(8tw5D_LtC~uQz^gpU}Qgf1C09 zdNpj{Qv-Wa0llU6h4y^ZY;XH}-#_hUw8vv;Z=>b=dj1VY zdpy2Se}wbnmdaaZ`x~!MHGhTe8S;O@>gWAA~gA>6NQZ}NQn-Z_vT`ZpY3LjP9V&xGF5iP0W^;q$ccd~kQ$zdL_;-V#0!2=#~c5925FcfZ-^=XYVe zZmIm$L4B+FI~(X9G1}M5FSft`7}!_M-&^`T>T#3r^Tm3jz5j;(4C5tX~Ep` za6Aj+y=T3iXZZTMn*LBiLqh%4y+L)I3o0K+OaHK_2k) z_y(&}Q&2bG?}U8ufY0;&zLm>!Ha`b&x)O%^{Jy30hxuW8s5j*I{J0ftGHiB1hF;E- zy?!Abw*)VrFuKjq-s+6#X-LFwkwqfymY*{0FzhsRa68;?x3|R@lFn%8b~=CD(!Ja) zqhDj_;Cj4#rF573V&_+r7dHm4hx2#akF+Nkb_BGS^LA+u^1a?(zj%=Eq|5EJ1lQkV z^h(1aFA(r~vX^tZ-hMQAg5l=s%NYNFVcu}kFm4?%Y1n4iZJ0JR_K53sy54^nhW0x> zRDmyDCB4V;ml^gOj#Mb;dOUyB`K%uGhBFPf59;UkrmbAo(B&CffY2YF;c}|cZtt)& z8hUxJf2I7i@edffyqwVv9rnZhm+=fk&+jxkZP;Vz<=hXWmhRy8rY)hGJf!vu zCdmD<*65!Yx_=_(k81vLxj8Fe%|FJrQ9Fg{8B1_~g!$27NoO>y#P4=(sh*JE^^TgpUk3U+jP5jCY3TJ0*bD9GdOW|| ze;7KQHoC`f*wEuFX>r$Hr9ZkopfN-p1r@Yv}#D!RTu8M~oq7=>Dx#ZrC!s+^91eR;q`yd4Adxs+9BkJG+<9 zSc3b>?a4W#VTWO-p_fk^-D6k{>H`L+8!SB@aCZm&vZHDcJ4ldWryL`{DVa)?I z57azR^FYl5H4kj;0W5opE1x?1S*K~m3@Jxaa`2y+L)I3o0K+OX+ z57azR^FYl5H4oH0Q1d{|12qrSJW%sM%>y+L)I3o0K+OX+57azR^FYl5H4oH0Q1d{| z12qrSJn+IjFr;tfJ^gQTVcp*obab=r`|NX_e&LSUG70|or`#?_x5x0{l>NOx{~l}h zZTcmiIYIiNMmL%n{GO}J+A_n6%}+@o3f))o|kp`j-I~1{wVmb)BNS% z5l>yB{&c#Mp68oh&3{j-eFx8fQtj~fY!mJ3zvJxhu=#h^XB%IW8FVDj>-v0b^s??Z zM^ArBe-!+eGk*C2 z0xySSCq6V9-AG!3*&F- zX==Y7E9dV#KgW3fp4C%OpSn(kBkCUO5*I1Uf36iD8$Mm}1g}P2O{GIG~8-J-G>W3ogIvtR# zOI)NZe~&7CY_M7JWZtCySmV5Ls#|0??*?2u` z^MieAmAVV<-*e|WF?zfD`-sIGfA{^j#$RUEobLJg1KLh;ejc%SV}5Yl?d65;SnDX}}iHnr+XZOiYaqH#PaeSzCg22aT_Dwf= z@z>fKZ~j{X2xrnc){Vkv|NcR8{{fZY_}+KVM85Sr#moDd%O>*S6%%<{E^~f{my=S9 zub|5A`u4N-I6NoSF`b26mxzm$cC~dPk=dgCpH3{3>1JL@JU%tUv3N2S(^|&yLA#L> zkl{}Zwau*khRq7ZN26H2l3ruum;1}GUGbL7U%m`Q>??IvLRoj%D*5>A--}HivhmBj z<+zPs@f0T zK5#*>Rq+(_d{aWXQk_$I{dlh(wnu97VqK5fkNcou!&@%?J_r5Obvn3NH;T?urH|$O zSM_GipE>RysQo3!1u5}zyq-?qr=kw`vvW1e$e;0%qd}WeqlTjFXZo2(m!?}=J38JhhlH)QrBf57ZVey!P${Bg4%`3N{C71Msi>C=m}2Gg9A z>Q8rpKOTI1Pf`GNzcl}{Kf~MO`FA1eQ|IaQg>|E-?T3oQg7%sD%1;$&9E!#*M!lAWvOvqY~=ZB~NPSJn7|zkLzz-d^b)JKH~A|<=WZw(+nT?$-{l| z;&06~%_8U)CiqQkGw(>XI36 ze2jSG+*MreSGr`-?t4P z*PG$Nc>cY4n(-}Ae92YjU!E@<_5ZX{zuK>qgOP)mO})hb&*_zBzo0=B?x_Y`&~G^XI7eOR0u*?fr_Q znE&mHXTQy;&%T!hGl>A}M&YZ`I>`=;KkhrKMsJidN=eh@TyIEgQ<<>}29 ze|8=dj6N$~{N+%ey8O1|@T?n!uU3^L@3Q#gK}?^;ANh>MANkD|f8<9k{>aC{CNdnq zj4S$=^;`6!DtwOG%fC;#>7wQ0uTC4zz1IB8{o9D)Eq6XH`2~G+I(=YW+J%(m^{Vu& zeLq6I8YJl7)3L#s)ZzH`WZfuyjjA+x(8e#% zXGd-PlHXp;hwnEz;doUwb_N<~{%RO|gI3$E)?CYSi98 z@bOb_y67xZ%yvm>*K72Cg7~HjN0eU9FS|UPl$ZCr@JZim@yBr?7S2p!mQ`oFq_pc&y-)Vc8as{JoL`BHlz^70&IIju8cm#070xeF z{#~xxt6J|+{}T5ZHCjRCa{gVRx)Q`!f$Av{rTyqvQrc;ywle=EHox#3X!s>+&vN;_ zhOFwWkQTU){*C< z_@qB<_T&C2cx62My%3ezcc-bsKdc*tZ>{P}4qN>3d}qkwk9^AV7xK@v{Du68@VtR-Va|j zxqqunE^Yec`=#uw6;(u7H?&%8pXKs=51-_5^DpO-@W;BoksmVul7E5umwc=Fm%OSi z|F@ifZ&4MA)>ogM{Zle|LBYI);d{&bwj)5`}b*Do~-)vM1Qt= z{vG!GyT|kI@HF}N>5A{X%xlzMqejn`{O>l!H!EfKW4ig-he-A#u{teAF^e^p~)$;Vm zY<`(#a;>kQe17r2|N9S+r!K2n5nme{(UFvvyIA-sWj_G z;gj_VpX4rYe58J&>j~$-QS&ePz2;x?;XLgRc~x8fFY|*aeC?_tJ#S3&tI$-&wcooT zPa9a(iU{kbtd{TJyq2fe7(VtRd}BQSz7O@;Mn%9(n02G@ZB@DC9`i5HYg^2}-G|79G_^!KAW6kqzy=3mY)ao_*@2!nGeXe7ryWCXK&xzfY6JhK+5Mwj1}Fb))bdQbp(QxA ze=Qu27k^JcefITq%*nb@_&QY}+4NP7Jf2VNv-yvF_!z}Y{w|yU$Pe56N1j+)FV66C zQp)*nm)-U4hkx)~KIv(;l%v!Cq$XalF9!auAVh3~;;zZSC}=jBl^KWH-h z@%|gV{NP|jelQx-ejU~?-+o`y^7MX-KiY5jZSm}P3F^}Z6`Ib8vu+f=gQ_&yYW5?( z;HMgoff)T^xD`!2we5!c9kH)woWxLrwE zCoWRr`?lgM{{GOxx9C2E^V|cKpE9m|d=F8ewKZlx-XDHfJo|kF1D<_79iOvq6uyV5 z!1=>Aerv1|;$;3e^pwo$G+(w(x%}^8iZ3yGx7w?$!YLMD{uj0WK3wso7kU02dUrhk z%KnNzIvwb&8-?!?Dv+FeP}eic583=oevi%1tlWXkMEe0$A)Mc(X3{-4Z# zJ=+@}!~de~mD|6`c%lugiHZp8W~`Qv z?_MoW?lJpup39j1$R9TQk^dL7ANiEok32z!G4liBA|>u0s-nc`zs~IV*kgc*kNq3f zi}Z7Cd~p9d^l$OThkXB#eWlJyDC^DX5=IB z?3bM)`w-{O@`(tI7P!{nD`cmptXC znkG#l?d8*t>ixRwJ^v1WG@gH#PmzCpT;xBd_=?Z>TW#K>oku->s_n=9?}etA?XsPZ z>&2`-Z~Rsrp18hIeyS<`>(4K2=M#ES#ov#mU3q`H=`3r2?fUe+dcW>X57GIV`CIsj zc=2}&6jJBu^o4b!sJvf;Pci<&x9WKd&k_1OUd5;GFXNAKM@qbp){8{zCvD!Fx~5}k z;`%#e(muY&D8BSPzn^HgU_|kjJ3rk4z3gi&K%Nlm4q3U6uSx}yL*DpJdE@skZ~gs( zxBl*pSbtmQW;{d_S>~t2MM~VW^dgb@)Xe(V@_^#+=MY~~FVesE@~gP#FVBKGsB5BS z;&H4?yOFZ|e7#5veR^i?cfh*i<9n>)OW$wyE6#tPi8nrmpqG7(XR(P=tQ&>zaVn79 zYvY6GCoMKU$lq<_gZvk5e30K~^sBpae?AXw0?GG{kz9HzLKR5f?fG}e^Y4h~->-Q79gXmB%axf^!Yw!dS?3cY_CDAt zl}*D8Ez{_o=$8$40*rO&h9CnWx%FT{(#mqQWzN}ZKZ z)*ZDSK^AM(^+j+TqRCn>(f$bp&lujMHx zt-lv5zI2oMm**R;UySG9Z{a|VJ}PIc$?mgm6uu{`K=Oq7m-xb;>;8y*)hxwJ{@dnX z@8F#IxTY&{6jFbUMVkQTUFi(qxOpANRfcZ2XeH z)y6ORAKLgOUzO7SkSErCW#X3e^De94+wZ8Brypj&FU9z5{c1e>qg-_ zrpl7L%zl&)exc);{E*p?{7=k&t`al>NS{^^!yW{Ac##^PoLuKk`2{`;qT6`;jM{gE8&L`PPrWmxOC6sle@cl}aoxj)YccCdKPOcxB1(`~kjV;UdgYA(L_pen`BK*4A zE4Jb(66E|G6@S0ccGBPT{5xWJ%bj1{ijK0crvsUFqwxJ!l_p!hF>yXgdGpI%-u!Z} zH^1zSm|sS!GpB^j`GtNZCGOv8?L_d+nf33m^}@&ZKU$vtvH3S)a>ItVoPRe@k$pgs->^6T9rotG;fVRKDwVMwPfp7D_e)wWG5W2U_3x0u_x<~2El(dc z|8oB@^limk&cC@S@^2KruPDA`hv(l`&%gUT|Ng@B??{AyhtANyClp^|!16uneV{1UUyfT+t{?XszRdS#HvSHoL5Pp``LkZ6A8OAp*`HCvTW)^oo}&1R z!uJ=&m%Pi1zY#C~M!onu?!{kNqxSdXk9LTnTtEE$l=5SGQ8jGm;l6z-H(hj=DQ3H* zjK6==iv;ma7mg^sj3?qECB6?DzSbYi?D#nB*^&MEkm5^M+4x|772KnE%Z-mg)MsC* zvl7a>QTRTr0?GY0J|0fxztnuX$R`yq`QO_3Am3`^gFMxgqvgiO|51EZ!FRQN`oEms zsk%SQ6mi^>(yo{4#bbvzE55tzeF)wkwcnp4KUKJ>r`-Dy`?cW&?ZVV@Q>AgwC^;T>m;?atc2~7 z(!X=`B9ZyY zT+n*+De*1Tiv-8-G{J~e%kjZ^i*^EKljh1;d6XeELK``|m5AqN&rmRnEUPim&duW{TO%Q+cvmn$)65}Q`IS#Ki4b1#Ko50*;e8YLmwUba<-;#m{FEQE^_P5Hj9h6Kk(u@HLFzvTHhb2OfRhoP5!jcc<~Io6HBw@3w&yUf2F-$UkK@+0P7 z^5>a<$&Z?U$rHognhY-|rRDs4wc<+*9h+JIju?91ztpa_qaEHPk7_^d~f`= zn1B88J938pwSU+&k@?Nc`ZsF+TWbD2?v3A3!&`3u_Q5IgZxp^|=HG*!e^W3HS&%s%rk`CHAubOC$qoHM@s{)N7a&g`(PouU){Vlq zMg@|g`IqzWL2vwac;ojHZ~UgrzpP)xSf43gPIggl{H|4eiIG3ftbgsCx%mDI`?5~) zr7tr7@_eoJPx1UK?UiyeO ztAw)dh*k3O-J}A^Ve>EduLsS)s4vThWLa#C8(zaLY4iQwTg>)-u`p8ca;Kdu+)edb^0&%=hdoPS?} z`qVY4fyxxJZWO*xs6evS{LB2P<+%Er{N3hX@~h3i$rIMW<_s?vm;Q|Tm-mOxQM~2+`$p7P6_c8$Y$5AL;roILB=?wqIll~>f5{*8 z*57sB`WrM(T%QSSXiJ;!A(m;*a@a@W^=aC*N;jU#*FX2XCJDdUfJBPDM6{YZQgne%4Wzfr$$(W>~;KR5sK_nSryZ@KwJoXfsW zYM?TOtQ&<-zQ2Y~a@73G{H?|OOMb5Tm;BSrzvTCsf5{V8)ioJjPD;!9cZ=dngpWQu z{VTu!ick7M58LQ2iurv^|H{8VpekJTivO@K#|0_NKdBdq*2m1O{q|VTh>sl|QGDt1 z&3?uCuPWaB_bSw9U#YVa%DPebKBWT5s_*E0$L9n4YhoY#6?Pc|EU*=k%pNaAH+pUeC>vB z_=z)%?|}K-$Cp=p={H*Z@%P~}7sVUD^83u}t2I#(VcjTv^7|S1Bu8!ha{u3A-l$;=ilde{yiSy-_&)PQ(`RVU-|tEd=jlso>~9W3I6!) zQGDtDuJl{EJ{w3dG{w4oB^Dp_-B3*yU z6VSmk^lzWyOJu^C^>0*udZ*$`f64sI{BqRrmdmf?`;n@G7ORZ1ZWO+L6-c&t{!RT> z$20wPm*?LXd;Z-U;op6k%qanv8@~m`ml&;{S^siekkYR5`y==yMlPLMd`$+3_-OPU zTAu!<#UJz2*306}FYaA# z$@n8qpYGCvx}^`_=u{?6@$lvG>?hy9q0YFNO66ELitg4M#eaXM)$)5j7u)OcrMjo$ zy4?3epQ-o~qqApreE9Pp`~ED&m(H4hc|JLGMLhrBg^d(_L!FgS){VmVY!yfzvhl(F zZ02_p_rKxeC+=_WvhhLr;j1U!kBgDxgYyb0aX&{d5+ieF*1vnKLq0xvzXqT59p+!o z53O_K`S)W}Znm{5$OVH@rs2ukYWgrDx{f=bL}S z`7`TZg)u&tlK&fxTE6BoX$((iA?K5*@%sV=n*LjhKYsr$Zu}jXqWB|DQrho@dXZ>d zFth&MXHN3*y~ymh*X+mrQLr%H`2Fe>*)Iy;ixpooW#gCUPseTilHX|(&8B(AM6(?@xAse!8f8fir{aBy*m;8S7FZuVFf5{&-|B|Oa z4=&5_a#C8(zpqq$iJ|(L_3x;m_v7#H%)ehV|8jpG+VjG4=cmV~$iGqe_L_h1GXL_t zbj18ie$@O+exLc5e7Jb>^YOjQGiI5|3H^J(Zu|E8y4mlSX20V8w=v%MkoV73#pLam zD`nj%eBV%koE7rOBGI{%Tcx=`&y{=;TJ@>rz$^z;+vZ^Yx%euZE^ z<23zv{1#|0pAzTu&Jui#ds5Pb_?Kw;E5hp3}y^8o) zFzptb*Z}cx9Fr2xE6);q%nL|~@1@%`Odf{%VCCB9cHz9kQ0f$|U7U*e%( zNeO3EFA}56W;TBzE>hxqjo}MgXBOY6d5rkzz#l2T^zXg><a2va z?x;`wCji56Fb{65RC$bZT5EAk_jUy+ZCk@?l!2z)o{kM!Hwxd+ zR3N#};*aM~$1VQIpRo8N{|$>j@-5eCeosCwR>mLwN=n?X*Na4E^UV5}xJZfb2ZnES z)y(4C<;5TI-J=)jc@}@fKeRet{QVsDsVlBeOXXNM3f~V^AbH5*kMfy6==?`Me1gUo z`9l_epU%)iWMhs?j^Gv;6N-#7n~A2t7y zkBhS0_&u!n5<}}|*1xT0Ncxv{mH8E)^xfY04cEu>?}K14>WXW#QaRR*!uJamNS^aw zIv*4Nu=$sKi}{!QJ?3BXK}P5AB2N&jGrXFVmhXW~1Wo@6Qr^j5|`=@406QKE@p>@jde_ z!AHB165lHn-$TeN`@sG6Jjd`cP9HdA+7j{5ZluIFc$VOc`u^5k3N(GEw?7}X z^U8A17cNBR&Ay(F?O8Vp-y2jQxyswWg@0^u&C%X_z5UzIy#3oy)c&muXzt&l_DAx3 z1)t;|!^ii7h7BM2gNBd%QNu?*SfcyOB3}j_IIKlZ0J8*hL4I1GIDl{zb-tQ&>zxC$iqn}3VXKmMfgSLC0l{v-ce^Dp^U^Dp_h z7-^5tfco}(t(K?1=bgU{+w;tFNCFh40OZ zFWGAT<@*X3n5X%WIp>e*FI1NSoU4 zt@gbd;&`;lU1j=YKIWOV6;(utBMM))YCHcfvmfQzk1(rtsi@Hkgdc0NY>$*Gdh}w} zu!nEN{7bx)pK6*kh0O0r+3xH0{zF(k)$NzlS1$j%SAiz@{y{l?Q+=P$(}H(ypCpSLsa2!#0l_AJ2{HGX%TCHQDR-@h*@>w)PNU_MLx`Tl+R^fq5sJMqze zq>R6}*!T_ZnA!Y*Nv@A?Sn;K=w{?s8(Xinym;WuCqWmBV-&+-5@@`u{Xzzozevluv z{D6Fwbx0`=AnSXhIs7vvd z^Kau6`8NvRJIud_%)cDpsXw>49+9uQNc~6t0`o8VA@eVJI7Gq_s*<;_gja2|GwM&yWRZDd@R!!&%Y~RFjYZ|RYqAi3g17gK(guw zI$oKN4V!<-uQLCVzu5ds{a!J81TzKU?khv&qLWNkt;G z-(0Ij|5C}X6yIZl@9O&M->2Mk(JJCHgZb^hSIg7q+Bo3v17`-~`FHIU`S+X%eDBlp zWRuN*%rB4I{71ge=0Eb6diiN)`Q-U&IlFWIE85Yk@P5TtHMB$R<=fX2pK^AVX=J;k zX3pRPdXWfsD!wTPP(~rw)4d)};@hVe=?g9XIKPY--g5c#%~KSAQTYBv@g+OF_-pw~ zi|Y~NZP<&yxnBGYN5tRg8Lq$oYW`*Xm2t40ZlwoA(Le^90A z8ZVCz{zlika`@!=3Uy5fzBj7Uj(LTwtkQgR;ay;d`z&+Uz$wb zx8M7<5Q-D&)c1Vg5Wc6wiK-M}6u#9ptP_zml^2?W#06;>F*H7k`Jm_^bBf zuj&lrkGM#Q@1179SDF2iW`vRF#k1cshp8ZBW`=vbl zh0|oeVZ~SXezRZkS@`qg*-!e*zN)Iq{<1FZM#}QHs=DI&M%6nM7sv4fwLj;4;p6)! z#h3oB+3y1De&z-7>?h|$?5kC+h_G%HzIUm%RUZnOrV(-5&kDRXeU!w5+tKvI9 zxJT_pd$(GDxc(YD<%H`m+oS#1(7)-$taChk8S^jkQhusw(iFBDB|f(MK`l==d+#v~ z?@|By?>D{Ma{m5VVB+Wm`H`@3hzt_eG z`M4lCzYrHGaeq=3&EH|;gY)y~OXAsY$rRa-xJZd_MDd+B^isuB>^B991=DlEe0`Ov zCqB0JDZQAr%KVGtGWQuV|B|=jDJL8sp=l;Qn&;D6p8iMkFXy?Dm&Nn1{5~^voep@` zjl%aCRhn!y`*Hq1?yVm!Ha>X&T5tUbR_gfS{kT}mjgQZ&qWSyHemoBeUJ=iJ^856( zL0n9wa;!_glCu1BDtz9s;VIUoK(SzYE;v4P^$Lo=-_F)PuNSk1%)fj-m*b~SeDIFnsXaLUdp&-@Y`4qr8S_V4?~S6K&d|Gny` zU1u9l{71a@er-JEe`h?~d+*pp{P){^+V5eW{&PM4Q64|bQe)xXlazrf>bJo#qh+5e33+|O?~SK@U*Pe7F`oW? z(20rh{7~bmKjHCJ#&bMeVm$3T-+1C}GM@9s3gbDxHhT4+;nlyxc;e|Zp7wmE@$B#O zJb8b9f3e*s-dB74n~dkY_Fm)Zua6ne_C8}g$J3WR{u`eB_l)QK^F!m=zn>aU|17tC zF69r~ea<)c8BaXF_v-(~yZ=Y;{-2Gf{+q2m;>jA%`RKvM)4z}Q+hKH z=U9bvynOvJ#*#uLx^#&dqY*jv9Y_3F>{_=TQ) z6n^%PekLCJf&L~A+SA9g&s%T5;^Fbv&llTz!~E?v#AyRSrycJ!p7A$iJni{19~^?uXO_5EYU)4pHv@ch7dj^ns^7TG+(b(i(Z`FD@i<9KWymeP4+%N^}W-0#?SkWr@jw* z{J$H|_CH}fAvL#PcKLiRTv{@Ar#1*gkQyJ?f=Cw#)dZpE=(B z@%<2czD>P9w)uwoe_=fJeZ-Uhr18Y@1>-6Ib>pe;8{T^Mb#K4nzwh=PyU+1+ukjr3 z|Krskwe^5@_>(7}c%;rZydM=$mD-(p7MgzMfeUd%_J(;YXZ{=|UtOX6LlH27^%tg} zc8SveunOfL4|$2bDE$|lr}nI~1}^}W{@!Ti+y~h0z?YZ&O61#2Uh0>As%$^Wyj#AP zC3rp_q5trs6wiE9BJnT!ANOc2XB^q>z?YZ%mB@=f#r|>i%RWf-Ki}}2kL$DH^LoR> zaq(8;?>`&B%^(IpJ|xah<-fZhqj5IdFkcQTx%pSEoOy`d4t#m3C;H>c%RD3U7gTAx z{`|7u%I82vKC%xL`JGnI^+E2*$CsD=sO;6_%CCZ~tR->fW&bDRC$4-KWGlfh^UJC9 zm&;FmdkjFI_$V&k=Ue+9GJE^;v#}<$nfQIeUsLe{Y5Q z?}L0L{rz2q`u|j+e8Td9xcxof*5A1DPk{U-z!X>hiIA_vUr(t}e^{aZ*^rmHGcNus zAYX}p=RscfqH*=luTcLY$X622S3|xM`(Fq7xfS5gRH%PBR%0c$ra=3 zzoA0?^6#)$Vo&*ZizUB}i%0$)xk}32QK9}D;a@qciEAJE0%axg@?LNy z^4C--KL_%%M~sXAk_z=-RH6LiD#Y{13gr)@ucxx-qc5De-)nt|#cYGL-w?B zGi<9pcj`{UKu-wb)l&-R(TjF-}nFE9CYCGt-*`Olkv|NPX~|9XIz{^p;k_9=(| zUVw3Ds28ie|J=;if4D;VhhcC_e;*uE|7?@L!sN^0Z-hMlAH?K^;>y1b$}5q7Bqm$&Z(RMCLB10G?}EJe_m5XO@0$>p!M?`K>N8Tl;7f3jZRnNoRw z{QB~1AS?TuxbnA{{ui76^TC&XeEoUc6#a4KcbNQN>J&eQ!-UTptzEu9`8M2@{(i*D z%gK+J{HIJF)6s;Fum49Te;}s*3jjv&{3fRS&){5%|3Z!SSH^zn$H#LKge4y0%3lq6 z(f>x%FLQh8$JgIy`s2#)#$Xixj+lPAF8%oWKMI43%z0_;uZ)G#k1zjn2#ZZ_H~Dh< zUkL%Rf0R7W??^`uuXoK4|6J+It({8^R5hsR`+$}EdwjQrDU6(`5v0q7R{?6Pv-KCiTLzR&PCkQMx&vT}dC z`tmO}`7fHhA1}WA>mV=j{z#iY{CM%@KMz@%18y~WfBgCK_d-@|o-=uW{q*JKd`J3w z%;f$3${qD;&-sv%&m8*A76d}Zo2q1dA@JXI==kEGV;Z7Vec;Bra4=V@`}YoT8-$CWRq z|8<6Qu7Q+)@KDC+CnNCt@{fSLtDzcl^lnhZ@uG$Fr|r z<{R;ElzfBfnGG5Fya)fu_}y&f{(jY$|1ZdjeeN{*$Kbm3^E^-gk70bl^DZm**OULS z@_D92zON!S{DYPI>+Q8oYG<*dd}yD%mHYnk<(Hw3=zoLB`*?i$zk{sU{GBH6uMfWb zJ5B$`P2RW9e^@!=rwijn?D;1vm$6X#d3dw-m*aOWZi#c1G3WJX2b9M*T*m4)2T$h4f5jOxcawMsQ*k)e#9Lf>ng2v*TzSd&#J_RnCEt@dGp>9FvX$VM_fspePo@1` z4*iw%cdGGRXMUG=YUMM8{{+u%R{kVhmwt9x`6X6Zjo_E~zr?;bBepI5`0^hFNOoPmDC?DGU%mwtTvbV6RndtCVfvfBj%ea+~j2rRqORZe?1ErsL zTlwWyD7ljO>#J7o?|1l~8_xy&?=$+pWBCby6}&$(eg1ieFF()xbHd~$*DL+_@|Ef@ zCts;ODwRLm@SkmXDz)cd;3ip zJJb9BO6|$`9f^4V;{P{&mG)=4{adO2|JUXH@2l=aGB4+cpSJnXKM(ZZCw?;?sNo1N zxc*w5Kg&IjyWaGF!{p^WwDi+Z%DC&)vLHyUm{=@j@)xG=b^r#GAH>dX~_ z%i6L#a{amfZMf>~DGUUc<$88!J9}_Z7~C?jyDuMHwqfYj4XzZfj@0 zt34dZZ427-TXTb517RQZbms^1{e?j4b+!kqdv>6e_F%>OmBF&!?(RI)c6R5sb@t@? zcL)80Jy0HGg3E*1vx6)CCb;IB;M!}0E3OEh{p?`g;$Xqz;Ht$z^}(EM6R3vN*UpSh#R8l=t=a16yx@zH?hou(KoY{`BXA&aF}!6n1tF zv~>hMy#wfDfR5yYt+}qQfsX#(!EGJVl{|z7`Ul$vf_%?lcd(^%AnUpsUAsEhh%P9vSD43 zS+QZm`VG9Za>=G8YmD(5+1Al{$M&x7p5DGY`wIhuJ9h5c{mh`QsVUu5ldccab#*m$ zsRmrtq|>R!RIL;?)TC>ub=g8M~>bsky1S4i2xYZ>n#oYph4h zX7}b)b6s;?W2&aHF%5^Lp{AxjWiF_LN$MJF>r+il4RB5k^wc*3oq8jkZmeyD-|Oq@ zYT+oj00sm?b4*Q5Z9|j9L1P*&Yk)UkNvLO7);BcO)uJa&4XGyluLjZ7+|bnA%wSD7 z)un6DojOYD26nj0CyHMpN@0z?=ql}@D)agBB9bX`+DBfGICRbK~`uvw}m zl|l$MHP+Om>(T(N9<8mfPd5Unm@ZYHLQFQJs3Bbgr_i~LjSaN`i;<8@rRyb18yZm? zZh=wN6irP{HI0aIfWz3Vhc#+zQN6yl77=YOudAy|HA&bvp&f*x7zOpy4GoQHxX;Ea zMrd6fh5}*^W2aV3BN}RJ5g|<+Uo|x~DGZEMx(?$PPOgW6L$HAD!n5&0hF#;r_YhXS2GKD}u1yte?t-(k{#~Tr3F2Z3% zj4}*Y1Qnc(nZv>cW1y)CGlzr`B3?oOL5Il$okF~$?-oHA6wT-@f)xcWtPva-+D#Z* zjZK(wYB|blFx$XP2wjAii*ihAm_+I@*lL?G8(|pP;BUfZ1LP&rT+BC0Xf_})QfLVc z)i!WSDD3WO%eHmodV=cio`LF})$7|kd#c;8NL9D}2Atmx|M>??E@ zf}0ARJ==nHz1bx$1z487Su=oTE&*7A^?x-M>W#MiFWsGO!Rin3rNza6L%x6&zYr|f zr9ay;zVvT$OMiA#?{=)clLJMfP{{XJD^a+s-NKz+o^R{Q-q?v=Zyd<=wCDQUwbKKl zt@D{!nXkAW1G z(7a|KxPAbC<@$Q}cY>@m0TcE}NFV@~b@dkVcI}Fn_UGER=LhTtx#F#S$qiihcDYf| z-Pz`@TJrs!u(70)xnM)CXInnID%Z8uE>{kAb(K0${dr@4_s-t_b~;`Y#FdsHwg60S zTRzaB4Q`Qq5r2>`uHG6zDA=g^BD{$)fglK$Ny@mgBxS@9Kv*u{-ID{#V2LD&kQoRz z6_do}OcF7^T#_h`kt7jxgC>c%TI!NST!@rQ5|(f77~6 z;-$gDKyP>Pmc}d+>w(Vh{DiTx1-Ps2hMRhHjI8P1wk_YEU6bFD@4{%_GPo_cCD-30 z^TW#AK&~r5W}MHpPf+D1E-kNCV&lz;W?SH+{?38jz~0{t4QqOL2CEQS&dBJ^*52$o z>=W99zU|w_%L42|I&&D5;tG|L$*bV;;(LT;ZcA4_*obVuD{p}cws)|?v z&JEM8osj6uVFpD64R-egef_;{`SwA0c)TIZc6nGKkUB$!gK}%O|0NaM{ zENt$V1(;F>0Ap!qNjI_^WK%P)dSzE{uCz}Azbn71MB=G~Im}EwGN+7J+lZ|JT+ms_ zwqY|Om*e-A_V#w=v6hUNV8$-tw7k8fq@@@8IrJ~zmG3So#ym67ndQxKbuC2`IiIv6 ztYyK1>0S7CiD8z>OpQsp9l?%Kw-OPrSL=Gw|8=?Tu;VSAJq>li#@&UROB%&gliTf( zWDA(kOYW?Y?bbL7VPz)Hw`a9ZHx@r^C$W3#&h4@*7^vWu5w9XvG!Y!vX5-UTjhAoi z>TD~O!>q+UY>ANW2M1-@#NJk-5)JpsMAj}>{kfglLFYj**jcyr_U#VZbYx=xi;ZZZ zH<#Tm%bHxsP|Oa<&RV!GOgZCCEyZ@zE#1Kus7HG$D7#QGSHbN^d$(>a-mgqq6VEdZg2<2`mUD>${ILF(#$w9U8=K7OWjnXx351r`g>}|VGB5oK zL}ppOt4jr?_kgMJoKcG$Z?!?#MHbd}xChgkD=FmB`>u{c(2xJdyCIg|h=phn>s^1> zZW&`;y*q(mun$#B1ToFDXKzMEhUH+q`i4BFhRz*W+iY<48Fk*3@g_6MB=S+CZmk{% z$QaGam~YPqvM9IpVlubM%b2d*?%qMHpl(9TuFc&6U>hK+SL01e01%xdb?Y!Qj;}e{ z6}$TSaS(#CJg!_+;{2l@eQM82364Q9`m$TyENnM|mfiy9b0q`U>rG$_vIZ{ZQn!w+?&*|dtq)@r z7b{UEDD<}l?Y(UUSwyn!G8f5(Oa9yyR%4vbpd3ThE%^nU)wr}8E~8D|fm9(t4nBYx zWv8xtcGn8?$c*INMw}pN2SO`*J2biJKx=l zQ!lx6Gv9bMMummH8YFlwd7oWg*o{aBNTZ>G}R% z#JSceEm=P}kX^quyH;Db6S-nskYCcj4bv?;DOeUv6zaigeT73uoc1F*4Epz1C7esmy;AU*p}}> z1j4n5!hR&`+q9CbF(Qu@i1JR^$>j(yoX!=)$gNTm1{&Ulf$o8cfi5L3J~zsR#HYJh z(cL$&+b(5rxWY}C9oz%IL7UWUA$bT_Z^hotR3* z*YGZYFGpZ(wFLn)Kz@;Zx+k+0t6LluUwBMfJr7|)RV)aDcq*ZE$4;R~?`RsYD&2-aFVQ8;KiXtJQfzE{vHy~EKC8PI7e8+3C}>njArtPZar=zy zGB?{-%80@cl+B%eV3j^Xd-|q%oJ2t4U z$n91P6}B8IEID?!TWqmzx;=`nyjZ0%dtr*mZ{3OjaEXwv##k*d)gw!mzz|GYxSh8soOf!JdXZZkgm_TtGhWGDl})3|3ne>OM5PBZ z6S6~X6lK``yuo^%8^FY;63jo0lvO&9HuxmH=>f4Z`^F2L{&G&0%m!4u2J+a)@doY5+JeOU;(g9DaY9-yA=%}naTFa=B3LWLTDB! z4;o!MsPjE~sQU(4adqZ$;H0ih6xh$_x8f;Uw;|H=4428H+g*7%*64NXWB~^e?ySRA zvuj;KgXPF&ah4{dMY2nrS0R$t^djRZVdW48D}qeGUC2bbWILcI9;fSui0XDi^~+81);rXL7!|Fx^!D2{}2Z_92i58!Fhi=#3$ z01pV`RKmA#A`V56a1Y|#Wx?b(qx)%-Dl)vBd$L@z56ot{N7TDWZ}a^~EB*2u_GAEH zk%Tejdf@txJ`BJVVOy@PBOl7ziY=W?!&2OdLDvs|^f(dhLv7b8rEbT`_Jhs2VDpw> zb3WL-HQ3w}Z0-*>4+NWc1eD zt%5$XaL*cqm-PpR)OZ~)z;>*A;-Nr(S5E9!sCIX(mLu*E1v5!@Yd=mgWk(pw%z(LQ z_KvX7(L0D&43J6;Adv`h`rjM6{TR+Hip^(nG~$*gSEg7JVjO4++^&xyR#frErzRDf$?p_*vH#JQg6e7~fAegf62t3#wOUDy|dH4CHgB+e}n zOE2^_)l8y0EU8hu5G|d1F3yeG;j@`dmisXNGgIcip)djBMe}t zs}K!SD;0s3C<CR-EByMi*9R9b>nYs zt^1pHe{1A#s{3}lEkP~qIL^F1m|Y0xLbj%RcBcqfj9#f)W*4MPb&X#`wpgC#bts?N zVO^)hBWxp=)g}Bzw_VrR1x?pAc1W8OZV&Xzar`y2!Nc5 zr6HhUh3PswAVGoU15?F7uCN_>mAYJJX53lmaue7AXhUo)!fP;TUF*WhozOF;PWG`a za!5ZIihUTXFzJp{$`1D7j0r)lX;q;@`S3CrtXvarcxd1{*^6$Q-MR9j+irkAF1kH^ zwVb2t5K>#gbMRZ|l^1P38TgWQ8&`)|zA?cUqj+|wby$MO#=yxnZFssqRw&;hwJVLN1vPv0HEN?GEL{8h@}zVW1#| zMLy@^s#DRe)fCT}(ap+*5Q!Z!p7w>|CJeR$627i3nVk_V`Obd8bvfanTr|HG;)avCJb9-=5Wm1%j96PdWksP%`==O7rG%X zyTjXcNLyEBd=Bc%2MA267+t(*;Ay#z;T8Oiro=!Y)Xb)vbK@+AR49(d)76zeP8*HXW zaaVrp0Jz1rtv2ih$Bh1qCWo=5=cLXb!tX|{(EQ(&fr31yqyGt62$YvJoU|)U6?aJ+=ktfTmk*u+3kyFw_mv^w5Q~Y!qUQ^ z`l`TAJObpH$eD0%E+>FE((9D8Y`l?hPIiuLbOn#?dVB$CQTyq@N}A=?MQn1g`@j&* zL#^yYu#3ayP4;w9*zV5WaF~yi8hztd&f-e=>^ArOV$#xrJPT4&N{7STRRkbP;hT2Jc4Nxl;&6hTblxfR7Alvvq{ z^GY0hxU-7V-i-GaD;Wk4j_}8|7UOQBE(v2q*J<>74nWVrY9+6=S#Y{{gK*4-XAFA2 zTe7xgiH7TjIr%8j z$B|?@^gt;9QvT~>-XGS>tz{ECyXX~8iC|H7G zy!Uxj!<14Z-A{*jl7Pp5*uUdD12|o4VG@?t^~WW6YBq6LjZaHO8`>m3$2|s^Gp-;Q?;Pe1OksGbVv9Z& z$4M2QmEa7=J&BNcO`VDv2Pa)!cx)zV5eB-8uo9JefGV!Q<1>A3WDJxWKiJcc0V4-G z?tv4M9(jXn_e9MS%@}y4aElcp;Y?BqUw-Hs#MA4`>;eg;9?eORW686?DhUOA+o1=E z>T=wad7&Hg7FJi4n{!>D^CR*tguk5VsU>wf?tLAdZ6!m+MWznDuz=MX53@R-sZSb8>gs9nWc5pWg$+P)1mf7# zy(f%GMUK>+pTaqpNfy#AZ;Hv1RU0hR07UTS2WKuQ7w&q?}a zNx<8Wc+(D3DT6_CFB}`{OWP_Jd0X{5muSFgtg4SnhM%onNE6%(-1=+;4>a&xuy`$N zi+i;ti;WZ>b6i$BZMl_3zCSjxaD3X5wbi+JJYjXtW_ZaVpg6+~9{2KxCf8f=o;!xQ zdpwTJ6F!(zoYdwp_w#4S@ir_e#)149sd29>xJY9TI5GV=sXkT>M7p* zbh-G}&TaCd--J=;o_0u0duuFo;iDeMh{Kzx-FTIOS&&Rk^6TV7^li&MA5kk>kjwiuAJOk{dBK(FsCNdh_U7Tw*+caP*r zmaa=SZy>QBuOi0B^ioL`vy2?ISQ?~*U$Ru$O3a<;4txdgbaGC2fFU!~r2Q|Qev0!# z&^{>d$zfN7MNXdP%U3jQ8fSv$-3aE4KSU}HG`zXeD?6Y$_N1vquY1+CM6HD3)!)sc#ny$RAIF&9*w)#~ zE%3T3%FL{k%nmtyc^ z^}*|EE>58UUpWwIyzMq&hAr6ZbAfDHCE0Q-4v$lqL>KTXvO7CQYKC`O$_W+c$nk)c zNS5{3y~wu!c^J->;aX;5Tim;T#bX@$SKfHE9m`2IG1p=ff*^K>F2&s2s_MyvZi$O< zYDz3+IXYgLDD8Mr&B;9$dofMVMknyJD0BEz^sTA zfnQ=fnJZjkS*n$(WO1+UR&GeT#BV$kr(6+%!$02C?DmqTqPkVI7IzOP?bA=*UlmHG zML7;E?qkOny!m10z_<}4Gq)2`IcK!PGaWgqUSF^-9)5Wi7vjxVrLCoc4JM_*Gsx{{qh=`JDzmo3o6`;jc~^JJ zA>l4y*rB1koVxoW%r`yJBA#*Z@CCx!m-BcDO_><&EEjKtp?`RoD1TfQ=YG$ih*=IQ zOL|2k;IW^aB;pPp=CaU+p-jW_r2!WAw#joCzZAiUpHP^jy!V-S3UqRL2j0Gjs#AJz zt9(-l{=zFpXfi~SD6hh?yJaOy4$bT4hfVV(xvgmk+dH>)4%qSW?9!1I+Uc(?FPn?K z9(T)}+5L0ya1BW`4lwX#1AWtF%b+{6pHNyXJL#dq*6x97{HG5Qa_+^Ot-*k!Mz#jp zRd@P}*ObF+!-hI>_8?BpIsfjonzJvNyKa4W^NI~iuMBX#cJ;=MtJhs0E?cvHEA(k~KH2SX?|Ekkm=k zjO%hG_$JN#gs2YY$_RED+?7GNI1Dg(KkGR`st|noLqRw%g->|Xre%29S5Jt>b3M09 z6mYI6z>5mO?Dhq-Q)&GZ$kn3aA9?O_<;hQoDQ#WcgvS}!jqfm z#`9SGXW{E|N32Ttrb>HOUSg6L&Q1z>sUs(VlJ{z+Io@qm)V~GqTjlZnBBgR0Um(&N zcvy;h;SwGJAhT)9%Xfl`GhZve!P1HsG`8S{8W&WN_08l^kdrZQ4+rFdRXdIV`8=l+ z4{)K(3Co3h*~guCMt};{#m>9bjM%E}UjW)1Dc}GYA;W4kcUlx2!@@f1O z%eI_`2)>0i!It(S5x#wjc6mmCYzBGh4q4yaduj{uRF^ptW$zF6J@$z@eDf$K|n}yO*IRyES>Jb1|=$*`}e+h(?pUhi_!*D`N8a z)4g}4FYk$eSFhLC_V8#_h87}9y4^lMY}Kmm@*T)lw}kKP$4j>-hqOFjsFvi!T_6c> z>(I8d+q!zU1Xo>m+vakWq^8z!kD`OF+%tD`tlQhArA6AC2~srLNluxFPB@vtc4=$5 zErs9?tf(jB(gC|Qe78Zp!*%D=Ubj3Kye1@&s zr0jwJBnlv}$8sjTv+D9KwLqbx zbE~{ozIBq{;Lx|O>>Kg;l18t6*Hx}`B)Tg)(J?vv@Pm6b&T)8MyRVe(7#wJqAMS8B z`*3e(4zmmfNU=a(N>Q%8H;bP+*nYAE3c$Z|IjU`figqh%OFhEFv%+gq!*wpRxji`bDPlXPR$y>e`-C%MD-p(kxt+R=z` zDCKL=m{hy)5a(oZ%zB-@)jRQ%2z*ysuZy#AsqrSfCyxE8`-XtLfZr=$?HQlKweeDc z@q3dhb}sLT)QM=lmrTtqiG&57OKL2~LIO z3FgTUoCJUA;j0`?;rqoW{S2NvnR4%2NnRswFbD>TN%tPlq*WpwzS=m{Ly)MZWU(!6 z30=bhFuq}b(!(W}3E?aSPwP5zlD1%1T5`;G>Ss+8&%E7F|LNhzO8Lr|91QE3+=QOn zgF1zTsiY9-(9p)k14DvW*r%O68_oz0Q3;(U2^|%N5=L^`QxDv^Z@8j zKszoxGIj#=oF^O^J8ur!yZFdh3iQyEkBqGWz4g)~V?&@FbB>I?7xbLDN5(!2dKmQk zpm$$+WbC(~hf+tzTIQntnj>RfpiT8h#`c1CfWAZcYmSVK2;Btypu3(A+*hK#7aSSe z3_AB!N5+OhZ+#Q=gYJLVk+A^(P}`w>N5+~!_x~&8Ku>(=$k-mxjt@f~bQkCcLGJ=R z0J`TRN5-n~4{II*oeO&5zoQ+{RiL*@IcOhf$48+D^zP39ALxlM10U%AuL2+Fp|2eo z3+Dsx!6RdJpoc*>fp&ZY_65BQbV&Gb9vOQL=pNAbg5C}KSi* z$o=fd*yW(B?u9FIgdidwi2YTx-fD7~;pzj5pdq4aQdg5V6$5vH?Kkw*R7wBD8 zN5}Sn?wWOU>>q_DkB;pVdco1L?}Hw`@aWh%Dd2t5(Xq=xcg;UK)(*O7;nA@n&|4QD z9eW38N9O3*29S|=-5Nk zkOzGd=q}JY(EXrmKo5bogB}Lm1$xfN(Xp3kE1bZkH9;U63w`zC0|Jx9lmgB}8{s)PTI9vyox=;32W z#}0t*|26d1quy_!5A^WwkB)s1^qfB&9SiV7vgbVb*w`A-6XzToy9@NL#Idmtf*yF( zv9SRE5a5N$V`JBVp7Z!)V?#nOIyUw(&|RS42R;0xV`C>kJ1#ypR)v2EaMzq;V|zjG zu0A$)ALyQ%V`JywAKp3u8iHP!J~oyS9<&woocd#9`-N{jHkMk1cwKaCY#8+L(qm)a zyc+jcq8|Q%+5I;h`@gKc4}4_v_5Yulo86t=jR;L^5DX0lDWPdQZG+HY5Cp-fs30Y+ zRiVKkNZB$a2n`0oV9OxdsFWbBv|T!Mwb7SmNcxbCCI1+pYIT(GGe1YzlwyI5FFoK^d?0-i&fYDvzs-~KJ zIpeAU^z0f}e%UXHs|Xn0C$5T*Az%B&l?MzT7*~y8-=T5U0X9{{RsON)d*aFk1}o#L z66`xBu7aS?8&{jaNKIT79f$q7qzCq05LX*y|H8QH105H~)fnixJg&;U;8k%|2S$${ zSK%7;P8e64K+lQeY7C5>Jg&-5!vC4$suc`eG_FR#Wv7td zAIDWE=={^T>H&RV1Pp>BU}Oycwa7mhS2d@yzj{IiLHC*o)dL3BPN=-o$Zx@fDh4B< z8+7h4p(;Ut;e_&m-W?}Yv&eUvP+fw%PN+WN!A+ohw+R&oBVh6A@Viea`HW_Aj|o)^ zhWDIMO<><%6KXvebm0f|>`fe?bKeOyCi49zl%tNg4kLXqRz0CQ1&<>=&|5R1Mr9vd z0HY^QsM0g=Upt{1K=-K=ss(hMh8-9I!=R^bLTv=SXH2LO(0?ZOB0pIVM&8Ir!OaQgwmBeI`{=J$??DRApeWd{TMAaMPsn@xfu=Hz!pq7`}8; zg#>S(RKsB8j!DJKuJU|$PAaG1-IJ;Ubl*RzY6YK|R4rg1*a3PsOsZ}$3P!-DA55xI z(D~G)DrrC-o>X;$&rB*m7<_h8b%M_BNfidYU_a;w2ZaYm!6>)@CZEI4`N*Fqe$dlP z`e5WIq%ZQ9um@v(lWIixmnT&M^bMeY0rFpx{|mtg@q_-iNgs@Y-C*E1#0|RtHmSzI z;6EmnqY*# z#O}KZ)dBY1l2F~CCy-DP!S5wh42*yapyRfLD!znr0n5NhdqM?3#~lgPCHr8n?BAJC zn?Nr(3O3!9P)RTb7OW%PyRjF%C!wmqrtc?Iz2JTL1D(Ny+5mtXf_=W!w_TOS3ECziW6Kb98zn)NSVDJs{ z4Mu*IP*KqHCUIVd9CU%+-x43_ekY;)pyPew0;8KL7qD+Cp~{=Vl~bx2Oo9P0x@t;w zgAT`(8UVekr_?am2PVK6=(rqQJEbbXz%Em&0dzX2lppki?Vw|~Db*u<$&?xbli(QW z+-ph|T|qjf*n@$6r&JB--ET@Yg5mwAR2%4VPpKdn1N*?}0r&v}Wm75%Iu4#v#os2s zhfFDM)#Ulh@dNgO4WQ%bDYXEGFPKu!EAewV`2-`^5+B%f{geucylqOw!SKyfs`M)C z)=w!9=)DDhf`KWu9*lrNFm~IN>IVaN5{K-A1y_^)T~o>pdOD_5HQ01Faexu91q|LZ zr8>Ys=alLdyl+ZH!6Y~aHr+p^^8Mr!ECGEF5{K+RLb=HPq6ui)=m{Qipa0X-4o0t3I9Qca-$_ml(Z8={^B|1zaEfe~;N^bAve zU>GdEj&gaAc)-AaC^yhEH>G^7#PcEL0!H(uRj=%?m{x;e*fFifz*yn5D!LxK9j8?p z7z3-pV9~T{0DU`6s}{jeOsfvi@yTh`5BkADFaXBD2)F%sW0JW1I|@Hw_$&AUqsQO}bexDi=miUI zqCM1*FVK4u`2quABNzl*LHEhiDkO5SAB=%fFbT#%=PA?5aWmO(zt3l^!)2bfyf-PVeYzN(^PpdH4 z1P*{fFe*G42ctK`e-}UBrCz|MTS!m#1C+xp#MM5nyrAJ{{XEnpMaF7kUQf6xmi zz`*yXRmrW`cM=!qypM7Ro9?F_fbJme0E~fA(EsqXatA1ZfK8y|QR)edh0q84 zo+2Hv>6vMj1Oq*!^F8#QpH>y16RZK7zy`q|Pb=U8RhICyWr1FA}%aW>5jdU1A4EgJV8fWQpLd}SkQ%^ zo2d`bzn=O4J-1RHVDfv|%l_@y3*MPjL!kdI?7=_>_777YcT*l z-$?wzzfSyM^bOJngTEquFz_b%e~j{Yi~NJ0Uz2~(y@~vTvELBC><e!4@QSb zU-sX{{&DuB#1BUPO#Ojej2gZ|yI2b)T;2Yq{B4|?~URrz83>@}-gU~KPMRSkwqu?L&>CwgHm=)oS0 zei3^x@@3+G8vRP*2ZM)`KIp3=e$aaq_F(L2?7`?)X4OV8SUsyo1dk*AXVCYOKIlAt zR#k$I6K0hU^qx4YT0nQrtm*(gXU(c^+4s$=20`PtZI<`AI_>)F!}PV3W9yF%&K0{|LUw7l>Po$H3o)%I;$K% zBJRknDg~pzpH)?0@ZDL}DEogTKVbNM@+0zplOM2eg8YEVN%8~6rYMgd^rk5fFg7!* z>STXzR{3RrepYqL{sQew_CKI~fDU6$#X)cWoGN&p{I8r-9xw^k$-ZMwHG}TebE+L| zS~I7@vcGmtZ3JUG%&8a{-EmG8|CoFf&8c#*Z>Kp`115K#Q%$n}N$f%Yr?3Y-yUwXi zpnEs$!NBfwD*pxI*mF*~LC;=usv1o0jXmi2%$!;eM!=BlA2O%F_yK2?masQ$EmpDjr2kP>2s>+Me+a8FR`DHu>h%I?#O% z>4VPtIn@nDzc!}^WxrugjmbXfcnQ1^dob9DJs9}AsZTKW9ohpJzKQw-gEv#3U|>D%0rcKVeS(hLXiu*YALs`A?!X@O-bp^d z7}y3jJxD&lz@u|&0E~b`p!+e}CFp&UI9?^ar-%cLh4BMMpCKQhvzvU#KG*|Bo}*oX zfgbEZ-}BT*KXLw;@&uzVP@b~iOB}NQ6XF0PFHs+0U*DXHfc{sh583ahK7PvnPv=xA z*!0?*ss()m)Flh0X6#>s--12Z6sJ9c?s3{9=v|moLD2C5?HNphgJ56Yyh?y! z+q^0oz%GAYRe+uq^Qso~ubfxwWPjDX3V?3MyxIUZt)5o{U>_J2`I>nZ7x~(GRq%86 z3+Gii=-6dm)q!5;ylMtxV7u_U&a05@gZ*G&H~h%{?!+T}$-Hv>0)KnVt5VRn7x92j z7x946Lx=|qmJ<&cIFxum$I0_*7<8U8uaaQ&)Ol6%OUm{1dF2KB>gH7=7`Sj=wSmry z=hY@KcICW^f#Iv>RsKfOzZyHR$v>|=p!XW`0rr9GK>xL*FZ^|+4@R$_R}nDSHm}A& z-_4Zg>+pBZt1>X~ec}hBU?Z4(g!DnjGxMrT_Q5{T`Qp490;7GzFZ-{|tKv6E7c3L} zDSklTYs3NiU!PYUpy!Qw)eR=W2pEaXt5MMX*1XF56>))1F!?9y7wr2pe!%E^^Qr}m zjNnJ)f5#8#{|A1+z-Ih_-Yxivkj_@h1@z5PE}-*+c~uWOKAcy6uu09UPT}(wRFCk+ zf*Jy&D;87|jI3Nx#c$$w^@8$%&NU0F7WC}6pju?VXhC&=(VZ7mFBscnK@EbA0~XX6 z7y%t`kupabv5kJ`UFmZs%M~FlCNAU;7Le#6spC%uJr27p1Kp*G^17IcC2iAfyun}}V zOa4GF*be%^5Euq~!6+C39o@tWdcZi?1m?d(I|iL#6f6TB&r$!N2do4AU=tVyTfr#U zA@U#69zaJA^#J;wCw}3<7?}Jqf9t?O zYz-0c6aQ`ewf=BSwUTCDbzy_OqtI~_@P45~+k_C;|9ni@h36+R^zs*pjVZFKf074= zo4nrtj;WnRa#UeaE%qWG;jbUL4gz@ta!EJI-w^UKFz>j+HhYt#+h(`FTIg^ga+2PL z|Bb1OB;Df*J50XAZhX#$2Q!XJxcJzZYGPl05?d{@n$2UXR!i^$HX;ikJ5FTPg>5$b z(HS3YaQ$!{DSe5#6Pahrm|_Ss^#SqIjXaM0UQowm+@XWf`A60gA5+&ukw&xKU+9o{ zMvxC9e<&pvSpr$p_(w7qG6!{+Kz3a|JI56c8phx3QX*`0N+Obe89KGoW9k$${SljS zoKC+S&I`AGW=!26m3v&_fZhJMm=D;E0(A7oCsG^sALGxBS}Qh#b7N`;X-w3CVH}`U z*l5>@vvD#K$zK@VUX_Wt!G519A3$E2w^`AhCEvC7O{P4GJm0oiG2Ih+gZ;%qhmXxT z@^0j1tJpcNu*Wc-&}p)9cxuD`lmy&k*l$eX%PIUv3a?Dz_fmL$s(fn5dz)jk+DXbs z>RIPKFVDqp6WsdMo7Lx~d~_T5gIJ?;4p}wZPCHzvV6)lZP?ItbA?ru>iJM_?oVbkrHFQVd|K%Rnk_%VzZlGv94?qg$uhW|z^0ahpFMI(31~>Ill2dKyby1A1;MQ{Q1V`98!?F z{lhKXzgZoZ;f~atZvSxQq0QS({ zKNq>1?E&P&$iFJ>41L_+Y;P_sI!fYl8G9Rg)6Dajr1$J*wM$C(X8X59tH^ktR*t!( zQ^ZEW4>zl`*e2En`)Ov~xslgxBbOMfkar>9D*DG2hHUnKYhS|E!!`eCv${{pSklo) z60=_Ya0%8AZ_7yxop65E6gRQ0W4O@l58cQIk#k8y|D+5DkhQ+R`rJk3J%r4`n&YpF zmK+mknBsQ~c?0rPyKI(rDRJi0|D(tsl{jhN#)C8kHl@B@aP@tg)$?piEObqoo_t3X zbpGKN;P)`w5qNr)$C2N(6FZdi%IhSM&`P720bMpIqeWo+ow2k5)e-C}-`=c#31T;3GY%pK zhDDn`iVfK8-!^ycEh)S|g;$xVCp@BWzbz#=MURPis%684+iJ$%@Vl%{r%y>#8rzXNNvsV4TD7Z1$P;a1Pc~{}oDpQm;W|L&%htm}6=;vVnhW zRx{Fmj~3@nnW@}t+-W!dV-z|``e^a#hK{1!$vW(BNV`bu#_d{{BW^}l*Z*4dS))BB zeu-_ZG0JE!^9t%8u6vAjx})Yo6-JfLJ{woAkOE<2G%Yzy7cG+4w7vLlz`lU>-D@Qd z<{N7a18uWEw!-CoxLLhkX!>@TbEc5P*o{MQYW$P*Sr5L085`4-723D7&3?Ghj$71o zY~wq$+Wvr!Xf

q&*B@&KmMV&9p;y;{cs@Ib0I1j`ier%1O%SXekmG6AZ`A`F9$3 z*^T(>LKg+BsZ=aw9EZzUYyLzkFNX1u_UFch7jEO8tjA`I$&`0!r7rL3l^>ONBXkS8 z;eEHLJEZaJN+TK9(S`1#3(Jox^iWEN-@K}QT%)iUH#co_`FZ3K$SgsXNlbDW@ zjP@LE%r;t4xOMS2Oge+@Thil5xjq_}a}2z3VZF_8sN{SulL^k^qk^*QR zGbPqqbm|}9qC%N`1T*Ha!s({WVWK8i!q=tVd9B+o{e zeS*1ETc*Da!WBHTMO}`b#Aw*Rq0378%NSfU+y%l>cXkf0G=*bDA&97eK0W~dYw7Ej z?Q->!c6qtYs`1e-R~wOBO?I{MB}Y!H-3=>BA7{eElz4fI`m$NBZTZG$b-8*7bt9bf z)h+6E>6d0-rav83=$7+^n^&}(9r{Q1LN{I9>_10Ko@-E-2HE{n+E1$7HEr5<*fX|Q z*p}K->GtE7if~oWUoHL?cz#6AWwwpQNc+2XsVQeFhOw8CEj($d$cxJ!{Fm`u$tSms zx0-gC_HR!tHIz1b>{5~VT9D58%PlHFSyLBsD~tv%sGtvbXDtgdfn5eo zofhyl@a`6Mf*g~Vwi|MuZZ{SAOH0l?zw>aRWr*i>-d|!%e(6v$g}-@a`-(fvhPNqS zhIIz_#cfa8Z7V+7-s4%M<=Tqz{x%K6oR{@sTm8?>*xqdCr17Z2X5E)%jEKT_!e3za zmkyipRb7ZO<|p7{qg&Ls*`|D(Wg6VdX2BG$&j9o07 z3e%`n->|NCA4#^8{s_-F9VqQFQy=Lv?OC~~Opmc;l_}%SO5^7%v*hOSC5G?b=`H3t zD|t2s%{C?FkZj&BBxKVU zYC^UPC3((3m`V$BiX@=tx!Zz15f0hTw$jv7eB7L2l1!#Za~*$WpXB`P1D<*NC_m=B zMYs7j`$zR-e>1BZxa?yw@AC^<7 zk^J*>5%ybhG2hHZ*iXsH)a7DwIEIewwmWU zdR{f7+gue~2yOvxl=-^FnBe%R%^7d4&2C0{4VaDMBg`|5uPhm>x0xTz?WPg)5WebbwyIW{t62FOT)U(Vjul$@qG;9{->@7Wa4Yi{ z$8MZ|)NfTEFh{mbC$wfsI)ArJ$Gjg@hcEXfTh)o?v6u6ekPM|gwu?-#bL~RXY(=kj z-BxwHv~A10IhLgPnYC7V!w=>WeAQjORb9xO#?n_$!4h9NC+G!6&@vg~FEHM*94(}t zyt~uC13V|3Ywi$VI{#afV}cN0X}p)Ko;n%RdPryN?yc&OV{+xQY)L+UZy7O}DYMsF zm8LNDxrl~bL{5wU9|_~Dmcy$AY&2QUrZx3J&H^xQv@B?Cb+P5NSyQ{`w6gy%SNRyL zbNjnKcYu%OwhHHr!Z;&$Iej{JT4&{U_~qPIMY$uqAZHj~%UMiZm$00sP{m)!8AQ%D zc785r++fL-%#AsdF{W2oS)eyev5V(CJ6rY^^O{+a zi{tQLx2lUJHm)h!jn$kekha}8LnbV4#1-g7N4BavQu7IY9d^XLwt8d!N7rgZw*lSC ze{EH(7hhi@9=T>azv#N49IM;Wb&YLRPk%JO*2n)2!#rQ$UW9AW(Z0jDdYgUYuS+Kg z9Y*ao8cXH}GWW{cn|j)^Rjrb|aN*i8ROVVz30&>OR<)nnRsbt zo*2Pqy^Z%Xr0OJd{Xw6b3@kn$KOxuoc)>n$e=)9xr9o%%r00okYckgZ-YD9(5!RvZ z#kTHKakY|rkz%XcL{Gldv;E<8lVJSJw_me(&$!3FWOwnUKHaZE_$}ByuKtBv`APZo zB8wqw_^AKrcG8w=C&tOfqG~s-Yc&5{pfo4qI0+S;`&;<#-d(aq+9lB{v8lk zAEw%aVZ2X!F#Dwgsup=II>Vodt4Dv2z-ms@bE|D}XS9FcYEYq|ZnYVmxkf{{gK~|o zaQo%vF3Fu*%yM`$BiT9Es7PvE%S8E)WjI&vD2?G|VmWOY?k|=jo0*66ma!XHCV$s0 z6Q}i*I+flsc3hFSJ^)dkr>)M-6)ofQ-^=)1u8vM##^>Y9)E`SDa^~UrWy-pAnezfeH+TLr=?yN!{a~3`Zd{W)y?1uX$sKRSn8)M|`*S|^L0tWk zA%%HFt-g;{%LzMUPc2v5ogsAYFm=k!#Wm$}`#7%(Sq#4V!?=3#qcL;Q_}61B zI$pI~9hc;Mt&9VDk8NC?FUNALu`RUp9OoFz5@b$*xgTFcj&XIc9BZt6bu2xvP3~w_ zK3v#eYkAE{r!h<#A?LWd_{bb-v>mu)D1O>HiM9ic`;V}qWR6#L0QXPM8dp+2)J?hl zD}|0tZ2FLoB0q?0&DQx_dOlj4W8~^6FkZ~j(Uzy)&e24%n#nQM?f?uKjl z-ncpfO3L~g+P2ieM&tqHUrxzcxsVrQdl-2S^8L8>Z5eaN(mGh1yAGbrS!8k@&!Ws| znFU5oPU@dt(*E?EW#42jv(`8wM+8upS&r+{ z_Pu3X|LY!CmlWhG>!sInPARY|0kfXvoqcZJ-KE!E$r_kC^Snvk;n$4(9BCU%=cn9K zNZ+s8DeGS4pl#i@Q&v5SdURYYdEh@~EXR?egBj1zIVGiYwI*2v#=i8=a`<8RzuMW6Yt_ad^mT0M zhc$4aw|Hk8+r&^S%LBxw5xM8r<7%bkTkdDdQad)qrVXxa)3}PVEu8)L=31{H@&xjG zMb6K|pS&xv?d@^(jvT|J% zR;KQ4y_3J_9E2cO%7RbVqSIF-*REY8G3%%Uzcul3HIXV`yWymK<$k;U9ZB0wa=qxd zC&$&f=6T47-MCq2n~kT^8?usG%6A0a`k8TcAKSWoS>jsCW)k@*@-1e0q}DN}7GT%f zZ<4g6%v}^v_w2ZOJEh0HL0Rv3x(Td)kv)B5u-2}ZT-JKURQH>DY^&zS&9xZFqkt&-|8Z93rBEsU#v;rU73$vZ5ayqm8{V$jC~S=DKB1ET-oxU!iXw-9G_ z`eHW>*PzDLfhNcK^FA57BwS10gn9uz-Hy4}A#sac@u8q?BE8mP(3Vdd7eqS9+d?%xB-*o;$ zaIIy$mk-K}vtP$q#BM)a`DZ570g@g+7k`7uY7U-I$C>F6`&!bGTO(#BBo7I6{N=ps zj%~9a(dpO}uIMoGcj$!rf%ugB#33D~?yF^RgS>OF4+^f<{(?DXR3rC)mUs7=@~SH& z!7BSYaq2-P?+A5NOsFEcKDG4Pfbni--5#_2e8Zi&9Dr&Se<5s#zc`_;`e@Fn`xz?? zxQEQ*FiwP1z3M1Qmc_%L&z$`ukvxs!!+8|%1~mH%{X49$<4FI=`z-D8mGybkQ7pk>zWxQ{(O0I)oXSd%jVRf)Z zT&F4Vw4mcXeL~%oYA5-|wYrdn>wxR$J&e<)?*Y59C6jJ9T!?ot-edZvyWh4#%2@Id zfh+wQ?`340d{jwi6n`Vg>yck0?Lq3ZMGx1yt{3128Ya}WW*i}#afOaU{5dP|cLDEL zWE;B%StjaYvmALl@=az8T+iu|{84qE(6M;XsYA!p#5*$0Jj7Xdh+pc)E|2wStV$tA z2-)YBJ9bt4bzoa|3GZzzV2A5*D~-$aHI{*ub{anS<5tR(92#Fq;h#-3E|36{dVfcDSUFEs0w^?A=r7C;X2gj&yE5}ncTg!-jf79)09Q%Ed!xlO1yxIeUA zs82&j%(GG4Fq^2vzmLTvb-b5z_0H^YOkHc-s*huAaE%YgY_#i|W#d@1G=8ggFj%sr9sI_s zO38nT@y8se8BiBlPQAs-{$MVD_Fv{?9?r!WJLZnz@tl@oK>dtZL468?TTP;KVC&#ra9A091St-v&6R7(l?O_e?SWWjoL)O4b2Ru%-YEZe?4J4KnMKwqE=t#_vNW)tuPs`PPI! zXb30ou`W4mQpqz%GCt-T{kqS(*sXyZ`~vTQHRsU`)u-w>R~iqlP4}Z_bh^JXnSKU? zIXsKEuod6!aIR`=E)3V1gWCw_I40YU;XZE!t`)9F>Oz+Tr;2uCl`XY~e2`^g+^mq{ z$Ho{YoBG)>Cl?bE$4;sjw#V)}rk#rWr;0N>T!2?=-`0HrmGub73L)ztld9{73LveK_s!Ym@4Bv#fe-=5vHS=JLWGd7_LN zk_~Nv)ZTK!6nD01kb(=$y%g|BlQ^?fNpVYIsd;K|srgc$sXgYAF7oK+qFZmnq?$G7 z3n9DlxITJ^>~?vSBCiUa8gx3oKB;cX%qgCeD4A27u1k{H!K0>tLzX7Yl0?oxVa{nf z@G<<2$tBl*WNZi{Fa74EdW>z_ahuKFm1zqb;rih2Hs@?SxvSIVS((2SijvsJ(5bj~ zQe7ai$vpU6!e`UXUq07|12@pt*@mmqg;Qm&D6Y~&Se3qxPy$_!?gF~!n!4p$x12Sm z>J!rUnD3|T^yIFA8qQ@cWm}DH4BKC^&3MK<^EF)_1so$9;JR+& zJ>m~(E`F48k8TU`qwIPhLC}#B7}uva7@bKFDzj0PmY7eILsh0F86ERP7sm@J>t%GA zM3^aLbTV!-lKs*;8S224#B8<%k`(sEosmBs;Kd zNhZr_tJK-zqHBAIaAZAS=9#QKsO(l8&k{y^+a5rjoNWmuVweRhlW9Gt>UI zBdm`i&h)NK6kOXk>p#=tGqIJX02#G^fq5IFHxi+2bkH z^K?d}PCXvVSlN6G^D$>a?QhPN2JOaP^y}387j9s;ggV=t|HN&^sU&G~VYvK~gu2?~ zm|I<@IXUKRglpO(p~l%Z=aYYw^g}j|T`~_Eh4=2sw+DphC-cH2vW>{N1SI2q`L{?& z` z?lz_Nuck2dWQ9DLC-JSvw`cE!IY(t*;*jshG$QL`TaMRqtx@DX$P4yKWIcZwL0*BJ zX|2wW+yfNfBgpHK>*urC7g+*X)29>aI=1B}W#~AL`6RL)=4F@k$n?N-UA~s>e~=(y7}|Lgu2C?TQcwBQ4T3_yR0VEct0WLaVslt6S{*3C)7jc{Hw=qpAr@D zA0`@)Yug^`(1rd2`c#7=^=obHM9$B{Uo)~UWV^0pM_*$xpQq+x%V%}sY?yCi zh}-QaNJ=30sig08W7~EFWy-cW&zO)9^!zRY*LY;YycW*Mu1(M0&G<&){8fB&L+s>! zBC}A5ZvlDHQ6I%uhpgy$=CR0P(%911s#%L5>lMG0S=-3D{95D9MVGGGy|f^G2Txf0 z*V21tn`^@F=ox%>L&}k#o4;0MBWEVmAtIyP^g~It_U}oqYt0u9$?^`Z8y5SsK3=yR zS3~H=dCUw<{B&cH-|QdcF&nvwlz1v~m(Iq&JZXJTzO z0ych>l9;cOVCr3O)|C(2(eo1O&t?o&#vgQ(*7wd@;KJu8)O%9KTwhpi?5Vx5@jNg0 z;LLHgzF8p~_J}Dk@5DhI#AP>4+?c|D>NX(dxe34JSMlwVRoX8NM_)1qbyuNfow`z# zrHqp34qZ=trf$bd!=alW8)hfySZVee1W&?LVpDb^?eDvM>qQz5*E9=^4dykQ0^=ra zjN~?*6E-ehA@Q;C2XxGKxDNX&z5&Cjh}4T-zu;nXJ#zUr%sZyM)~?StYa7Ku?KNgw z4Wm)`;qT0a(?13zXp9| zO6p3_-_Mk!J8b&CglxztXzG@u+x=id-LqEf##b0_T?T9%s-&QZ`a(+ zb{^MW?B6kCXh9xCUMF&XF8%_@Mv(#NS@@oV_iQ^sAt)xTpQ$)gvhg0`l!L*H04Z z>u>t5s~n3ukq43g!1Tqr?gNrfeK%X`y%*m7Vj}(SGOjmp?FPAvzd^VKxF<9DJlm`m zo4)R@+f4$Uz{`9a$c%}(gkP7R_%7mN$=EB@S%$kV!!T+)nJ!&mF1W!G=7%PL3R@Gra%vMrz=2=;1{i%KbZy^C?WE z@ZTw9DkkrCOA=fDZxX80rDGc@FlKc~BL(IJ5`lRJhd_@W2zc@X-w5>eA5fpcLYU`G ziv(u&V9a^y|9Rsx=@j2jrL1#BLP>K!wcY_iN+A=p!IE7w*pZZ~c{K~w*%UF9BFr*` zV2Y*3=i*x02j70GG_R+$tulUYR@W-yXXfa;%1p0qm3>T{fd4hoFi&hSIXGovzel$b zXK=1mIAZo; zx!5Lm;50I5YcyM{-F{ZOtP|MEx47!@BR{Dd$7vi7k#&ebUtiNnX|5E`%eT5-6HfR0 zmvs4vT@~CIoL+;lXx=5|R1cT8+ms?X{S$rpK4J~Bw?(3Ty{>(^*lid6-KW(5GGmX| zY(#dsUTBt04?3IpKGeG=Pn`v${PkYe%o5|vw z`0?kill%Hp>crLTr02zh`7$rIx231L^rd_bxYEFn7JH7M3z>Conmm39`Ii-och5`j zct|2hdKHIHsV|v*fQar$#Z5DoE8lu{gqRaO*)_+89-EZ;5VoEpr_|4+jih58FxT1j zWa2UFGLH%GrYgQ~R)8*Z+?B?|x?Xx#%0)~(uQc{sld7LGbjwfT+h?fC&&6LAvN2>Q znddXq{JzXFr3vVw(>jglIP0d&cV*~#^v}5XEVcEKP5_-z-<0_dEZzUwMTKMkiMriM zo9sp>boP{*OPxCz#sq1Z?K=XuzF{go-)oThUNJWE9nWD4c@lZW`BO^jR`&-+4Huh5 zXVMRmpJ|pmW8y)&JlNPr+G!&?@(s|)g|z84?C84Q=KW2BOW%|6V2%Z)gFFLex#SkN zz5I2N&d8-x>PZ<)tkP*)x~8-)$1QSZ7q?obBNK(3uSS4kji84`l=>|$F8xH6If^sQ z_`4NDn3eq1okjoSn}N@pRWmM@o^T?`;q>eMdR8^0+o!I+sno6%D zV_yH!Ij~~8^+Fz1LhdM3C{IB%FC=f&Ve7%gO-b!phepF}^v_13KE$zq+SI2h=^)W-vC+LRKCaaKlA%9YA^tl6* zA~yB-4_E&EDfOao{6xPQ*)X!CIevH8jJPx_J+8LH4ct4Wo=S6eJ&JUggA=MpYE|vK z58Z0Me|WL>Z1w^9=4%LmwuRP}Z@>nTe|C{>64^#%hlq@80#f#p&!V%55BW=` zyjgC|>ii>LA7p-F${ToYOP^0wAuo7fO8r{o{3Pu*;bd z&ea810w>?KU3ir5SenPRX7gpY&Bj-VMr@0|%6Q0kBOf)}F6UPFGgg^?%iwCCno7Ul zwkq{{TOG3(t}Z;KV)&4sq*afs{OKuk4X?zk?`!HuB(zN{d=LD%wlHlvkah9B$}2_o zkqt9HvyOY<7vL*WeER)s5#*)MPG!A|bOd>&g*=J8&O%;v4#zv>+xmAS??k?{q)XeP z`1ROcg}e*7ytA41&*5BN5UuB3oJ5;>YDCBRT&C~1nnb}RS6t0dg4zhAZVeq3@a;?cxQq{mP6xWnY8NNI%Cp zGaqrAUY26|Zi4Im*_3+KeRzggtc z&Z~8T3_~P~NYd*RGHOP?33**w zKXq+8fV`OR?OvUh>x;qV+GYcsAMVR3jw!E}_ahG>Pt}7fT@ORZ`;aegbG_J(A@4zc zurx5*V&)m~^m{iuiWjd_lyiB@=}$*7rw2J0qh#-#)TeVi8QD>6+@IsoCf!#yG;j?4 za7z8hM%;3anOX;z?tjuBBIs~;CVi}7?$V*#iwkZP&Yw4}&N0W3LAzem&iRPlTzKU{ z$8kROWuI32o8xTUZpav;b&YmBCFYYMrGWwys^Y{M(3mmC}9*j$gi2l)|f%TL-x zGqPc1JBWZfklB3=n*roQ$e)w+IcG8JMn8Mc!~5xHo2Ihf2{Dk*xv`tWi8(zzIvrCK ze-&$|m&|WUaUDlqgZ#5LcJzErk915!Zjx+u$m`NX&v^mYj|)CtuL`}^9j4VuroWKQ zeD(ZCnDWhakbe9(*@rHzpiN7e5VHYk8I}L8e{;3 zm$Do}$FbA2x+S|TLn|_6xhbnGLn~NRv`tw`Or;kxzVj{Vm)WMw25oX7o91KV;30f9 zd<_0B;Zt*CuG|xG>i7@u;~UgpGM1)$TxZEQ9@WQ97rP0#Ccb6; zDbojQnP1lDqiodSL$_0BBlXYst*=3!JT*u$OZzQHz6p7O$Z5X}gXa7X`9|bY{_+#u zdSrQg8~Z~Mh!3tjN`70A$B;iO`PFsIY)a}lef7|GfzEF$bO_ybd_ViB)OkzRm=#)j z4(Q3TVmIWFNrB~{6>=~MwqW!cf7jQUKbB9cJ+?FbJ#weN|Nm?HGKPdmfB19L<~#Y> zck$PY%=!6gWjE(Nj8%8*`lF$6sN@)iP86Mdz6ZV&+wv3H7_uT{H*1MG$H=>g^L=DB zW?eL-KIx(LOW+17r_K8iDIev?>JOjJoSTT>YGko(WIkjqM=aG_hb-^NrLs0;?c2yY zkvXeA(v!9nMz#Ujb8JhylgG70K7hQE@0)Kck0KvJexK+|+hYxa=*N-QA3eR~{*dIs z!HHxqa;60OC+)NZSsvd^f7mRWW_?I*K3m#qv++gU+nP^Lw|{h7s;8H%$E`=!foxaP zrLMbXWL?Or!(tTb^ei6BjXS*`Kgs@w;Z?bGHkkhKKV&G6g4s5Mz)KVn6gr24!#Y4 z*dmz+Sp~A{MY0-X&Bzw}Ye3eujb00~=r(!*WcepeXTE(Rv2-DGZ6oVJR*5W~hLp`l zWKGDVJSZEz##!XU$UBf7nI)QIeJxx&+(3rA%N*O-mzbL2%TDDuX!5n``Cn*_Pvq&Hfvmcj0Q_ ziZ0}v=~Awo3)_quIh<&_Cb+uBY4bhPQsBnj8E!pX^y_@vJ(b?>MuASxg_{uEa0|z( z)L3U2yaz=3i(yQQ14(zI*k3cPHnUB-JvO-s$fo3b1g`tq)R-sZ04GsmlSE#6-LxuN zt8F?~8IHB-Sc@+q=3Ay!m6;E&)p#*2he*=1UeT?nq=-f4Bd*}vQLQhJV+sio9ycKuNeEx11&|B(0# zW|xP=5r->zfa9rI)_jlPirj;|p6=k?ixbVQV>Xo!~Id*62vj?u`=b3p) zt;|bYs6~+XBHty`PMlfoWDK32H>TCU*O-1+8Skx0mzje>V*NYQS@+Z>kEL+F-!T?s z?Ea(eB;6{w%D+ylK69SMvG_1^Jl7xkF^|`c=tSR}Rx=r$`n1kHlD_x}pws)0X_d_A zoSUW7jZVkEa{JN!ADwlvR2!7`&UH%3LkxKW`Ms$&ZBKnWOt}1SG5`Fpbsf6kD*u<= zZYAbQxPdXoQ8Q-d#jol^jLmpbx0@<-8qg`&N`1*-lR2-+ywiH|`&Z8^TKXX0(doL` zfDf-?&Rd8N))A~R-Y86S18@sF%%~kCUow}}_u|as)-YVWa3-_#}>9)`b z=P#Yf*`~z451r!uXVj0xUeBSQ(e}a(!G+-*nZA{$`&y$6 zfXg)wi2XWrIT#kZ|}Z-8^OEblvl&W5Y9^QiL=7rA=Ia*WQqg87Bty8V{G zRo$8$uZws+aHDX!#xzN{9-V>PtYh+v{q5FVC!FJsY)K0l@3rhe|pjz{uX1=sz3Ypxz{u+y6J!zJ#ejqivP=22^mHJRgZCtPwvww=yD zT*;FkAGhcZqdW4{$LmTy94)k;r>*;HDO|8OdyLijhim_dbsMRND}QnMcH4%|roQaF zitjGC_{-V8h3kXse8oE6A-KTn)^>5YvNx>LE4Y&R&#$cei5spsGGjS^tA-1{X+2&w zz;z8-r`HNM@CWNSgK&-STDRR^IL{wHzFu9F(+Ii?(dFwP|0?4Di*^0F;M#|+W2}TL z|7&)PQa?Vp{J&XqEpSmdt7A26oq_-6){%1SK&O6lcG-x1x1_&i`7w7;?8me1#rK%l zPgvKp!%z85THBSv1ryfoy9%yLW!uU4Qx7+gm)x#nYXIH#_GH#P&WWE5aDF3cIgc8E zE6Gn<&ZCCm;&98I6XbD$b8L0evMrXt6|J>S*8|rJx9$8*@?8fPDA>Myx1n28n4N|T zKV5KfxaHd906OhEWyde}!*D%2=eCzv^SD7=^U3XtwG7>&U9w}9I`YEx!fo59C5=Y7 zsPp5~aG~3QZr5(fW#<*W==k@rj$;t6WKZij#^9><`uI2`KhEnIzxK9HqXI6RgR6zB z*~i*$9b6o4+cJ{$0&vBp*4zd-&%W7hP3#8X3iiuRPs)85t_N;==C<+yq!zbz8l`aF z1G3YQ^sC^;;I{1tF1UKQfdg~5Ke2B^r~Wh9`Ih**;MxxU`1mEQ2)fnf%cnJl&d6ce zX^Fk#dg{9(x4pzuhEC}7A0Ln8%ZF~GC%asPYk~88(VFXkEBI1&S-Y_7hUvuD3edkEGE7x9%A0 z_`Bh%j?Epv#4(7@u=nHRaG|?^Zs>&E{v;mPja=tADLWqFD&dMwPG(*26yH9$x>J_- z-HJ~9RO@mG!WExx&Go{0>ay!d=O1nuZhQLt0=l6yv;9ckoNe@{v#hxaxL6LZ7Ovfw z9k;}`4zB0y?TamlZp%5=<=zYDtt_~<=fXL zy3rl@c8>hRb+%Y@qi{`ET61~a46VE>yS`n-RsxrIb?$tNP8B*`*DSBoh>oW$|Pf+X&}-&YBy6EBj&6^4jeJT=~n^aXN1yAFm|UgQ-4i*Y9mH$Ab#E=&QNw+C{uR zbXtD4yiO}RML*B2Bl!-Y)BQ{982jM{H)hw5q#u>^Utiw$f}|g@_U*it@$Aj)G8Nwy zaE`Zf`xYG^IvY0S){(rmqEq$T?D&NX!j0zOdf@_tNy~cH6Us^JXpkdMBK3?QGVu zT;lD4Tgbs}f(sSQW*uk6E(W)8hgr+=R6JQ zl2Bsgd09OJ3Ti($Kdj+XSZu^?7W@wI>x#V zD&U%q%}&FG`&zj6{DF?5Pf z%eI%YakMk8oxXi#Q-N->&N{|gxUMs@(~!KagBv*0x=sUdoo89wZGh|ZWv3_k9)PPq zJ9l|Y-bT?$p0j;_!HvN!7h5Abfp2AxA(HPl zN&lMcd<)kF=ejn#Ooi)%tG>?KZU`<0x9#{KZ6OXfcFXp)g_64%Zv)n8c;M>3mt7_j zTOFMHHfy_PxJ__YeYhPietYh+l=yqlX}@#(;vYu0w!=EcBwXp;*_?|siaY2paJlMK zbUf&EcIMWRa;QhA{C?{+{BZff|4_R|VaIqX*9Im}9J0^*@;BKyO zJ)B)P!nxtrKay?df~$t}J(@c&qSJ&<;<4O1l14i^9UH9E2*Wl0V0rsZ=;Vd1+S$?XWV&$4+9RERbT)Kn+Y46>=Y1|aZs8i>N`7b^Q!8B6kF2>M zTw_nRPZvIW;XKdhE(_6#qBHbDZXJnj0iEU-v&Ti@oZly%m#p(!0q5(><|N%(xCOZ7 z&N*AqiN0!W-vQU!Kda_5bzt_u-6H#3);XpNKTU9>aLdKmj!xT(Im`3jFkI=%xvY7U#JCa8 zyXxa(lsMw(7Ob9AZ|um9zLpx7_u}{OxaeAH@dL~+o!0p%hil(;`Fzx&<1VqzM>E_e zxNYZul8<({_#Pjh4=1{P==Sbq9peyOz_omgNpwm-J!g6Sq4+_@l7p>dEQhN+Wcy;Q zM>p?K>vHhJZGhW$jxKrYgbP<%+x5WJ9B!TOO>nLwmd|$_o&G9o`+|oUAC6kyz8syh z>N)ir;*|Eo0J2HOSBbj@ZUk=Ix)rVoF8^5TJgtWd!;Oj0MQee@E(F(lT((a+=Jdmb zymRSiK&s?5`63@e9z}jB+Z>DZb1&j+40-wS*}hzGjxNqyPRN~i(J4bGUNfiOA-^(D zT&Z7Q5?^UN?#Qe?u0^N&jO;Wd{dI7IXU-|6AE{&E?b=R`WdTY5thpt}vMyw8$euU# zdDi6VOgepVrM@}K;GUOphi7Bf7*A)`d#;D`UTSR@f*XUgY8(A<1K*lc*Oq9XgP$_4C`q@)C|v37 zb6M-DT&O1Cob9>mTJr6Dgz@Fh?aOyHy8aI97#rYf?xw#K>KHR?_r2`4!sUhL%y-+; zm$IJi?8#s9Jo+~+-y>x9y$HUlpU6%}+TAEz^7-uYmA;bqDAyT&JePjXRXnUjh{azm+!$OF+q&Z6K%6GNq!f^S!Wv454 z8{xum+dl6i+z4FPzSedNa8>(Rr{{cv`S$+vS?$(^&k8u_Ve`w*Wqs)MR#^LPfeU;t zyG|tD4!BL9&$g3vyWv7#$epg}45Cx{rQAA_MgpB^<$Tujl44)9fpiYfZ7=Pn0v&JF zeERvgKb? z1+k046`yC_4)TA%d>YQ`*-RH)<@wom5^p8k2wbjn8qsM$r?oM+j>NVe9p}aK>Ke1Z z@hREc9MgufD3kHw^6aH zg&Q;Wi{obp_?zP5i@1xiYj=gKVHbm6e3taiV*Iw^6Gv|#k-X6>ARG@@1|TNCR{joLAUy<w72bm3+eq^D6l#v->eE&%pm$_`i!wrQGD-SxWpn!E5hYzVdtF^1eER zg!K#f)Vr6jY#rcu3ZHHrAek4&>9BvGKX_>Q%1cDq-H&}Mf#Ds^%SFZguZ0rm6g{$B z|F4`|*K_|btF2dW_gUq(Uam-Ry3aZTcfd8((7)@^Xo*3MfKm5`!F4u35<6Bzpv&Zn0_I4xu_Ihdg$`pRfA?zWr zF0hE$!Z8vq4L0cI; z0ZW19<*-t)_rMBsSS8rN_xyTu!A1nw0w9s%=YMQWeZB6Nm*zTED&OXa^`%YX6Ef99d~YoUXMTKz^0 z`hI+*+@aF>FO0DE53h>m2k3$s^mx9EcNm-XC*)7A@LP;zNVcIDb-xVCfU@|6>YuX1gAXy)1- zhX=2fIE}m=^<}*k-R9q3@cU)>if;3-41R+)SkZ0%9S^_AhAURdKMC(r_%(07VrB2X zIKej$`uF)Q)9>E}zv``4tdc)+T^fG3j99U9G3q6*ORe4KDYrN$e#_ujHhRU%H8ysB z9b5G2wXxLSTAU8-Kz}`P#mXIgESS7v z<%!Z3{pH`9C=!cq>+VE2LH@w1dsx>xVB zq!|8t{5J>wn*;yNf&b>fe{x%x&lZ}0X7`L8{rUMs zzQ25Tqdkk?M4o5zX&2SEbXYh&$vsD@zV#mwhG*yd)4rWg37j7oINxS%zr7>E{`0c} z`eU~B`){(L|NP#a{O3~x*B{JtYY$}s|BAr*VS)2Q1J_RvoF5rDj|I-l1LwhfP6Yg` z1LrdX=kLUGoNdEC14vXJKf%rbg{>J>b4z$l;|38K4xBR<=_Ut^EzTo-asBitn)jYTU>@3FD z&V&8;P1Lvk=XRc3e|$I3t^7CtM#l%!zmNVF--mc^@p(FM{i1;X*gl${X8$?*oBe!- zXYtvI=hpAPM17m*y~}gc-Hi$iUlFk1i}5vkYX#C9oG%QczuDg;a6TenFE3zkEYB_f#`E0bKYw#gkLBM* zJh%AmLw)lP*3SdzZ}a?`!1-xBAHoYS4xHb}a~r?^x1HY|h~NLV^Tz}6``>o{QXqc+ z+s@w$#P5IG`CDi=Kf(P1_ag?KQgoy@!kvo-d==a^aEsy6aBsl<3+{WkHgtebz{Szg zoe7tQTME|&_Z{38XwW;sMd9{^n+10-+yiirz@^~if2^e8UWMz24q+VJG`Kk2>2M3+ z&Vy@%y8!M+xSQZQ;O>FDAMQQ4_u)Q-`v|TJ?pwHim{6|?HyADiw=UdBxKVHsxB|FB zxG`|Wa3yf#;HJYJ33oc&*>LBLNI!F9m>6Yfs9yW#GK>xT|-5Zqum`R4t) za3kSH!A0N-;0oc2;l{xo33n7+3@#3LI^0=s=fbtYErh!W?lQQ)!d(G(CEPV|*TUTh zcN5%gaCgJq3wJ+UKXi11;MRc)!3~8Q2{#I^5N-_IIJh#nINa%Q^1oTng=>Rb2zMjg zO>lR@-3@m?TqoQ|aQ$%OHwbPWxDeb>xRG$9;Ksp~!NuWvt%G#K6~Gn3Md9{_I~?u^ zxRc>dfjbj!KHPHcO~3aaM!@y1a~vst#G%&{S)p^xD?zIaPPsr54XW!&)X0# z3bzm3B)A&5bO`ku&cgsP9WDlUHQbGGzruN&dEVdPN;mhs!{A!s?t;4q?jg9x;hund z3ho8CG~BCjZ@|3^*9o@_?mM_2;C_Nz0k`HBr~`28!EFi`hT9Tu6kI-BAzTUE9ucG+ z?kc$7;QHlb{|j6`+_`WI;jV-$EAYJI;Ld{UfJ?*u4(Amj-f*+vX2Tr^cOl$ma96%Wn&aOc4-gnJsU3+`9A2sayU1zgoi&uf62 z1$Q}IJKQ61kHb9;_Z-|yaIe6<3HJ`%dvG@)s*!bhiN4@y zw9j+kI^q6~Hu3@L@f6gV)8Nj6I|uGMxEtU);2ws1WOL6uDU5mo*Bj+D0B!`_HgF^1 zc0>A_5$`y{Jr?e@Ua$`-eX0DG{uJKsxBRc)!{D|Bp9VP{ZWMl>1$j2y#`t|J;(I&X zOZa^U3BN6`09ffJnrA&-Q+7Qc^%jPbYh&1b+p0q!9^1K?KR zcL;Jl{tiQK%ij^mvHTr{oWS2@kTdu@2{{XHH^jRUvITBm{GJUt2TsyA7jhn4GkzZn zc^q6Nejg8c0$d}0w?du?C-G^6yoA5oA#dRC4#+$BI}Q0Ve|JKD#NWv+P)=~4z)pIE z=Y0Y94u0>k5`7JvBv%C#uv{~w6{V9Fts>r$>qxjyBF zlv1{G-KLbAQ*J@I73J2HQb$C8J4)FzBIlziccjdR90XU$^D&fTDR-jWg>pB_DCM4% zdr|I9IgxTd%Ka&)P##EmFy$eXhfz+aJc3g8m`MC%lyS-m$^_*M${NaANhMZ<-aLcQuaa{6Wu$#f(g$sh^HU)=f}Bse5K`Li zEz;LQz5pp@D)V0{)0ZJ1gnS)R%2MXUk3fD5De3)`@^i{BD8Hoqit-!EF32lz-M5g^ zf5*{xi=7I}1mz6M8p@fJb(9U1vnZP=TPWvH&Z9hz@&wA0C{Lz5pYj6AizqLlyo~a1 zl$TS=|FxF*UqyKh<#m)dQ2vARW=i?LoO0dmly^|xNqINry_EM;K1lg6<)f63Q_4Mx z*nNib1k0?K;{FL%@$}cFtr2K~RTgvY#f291Cve#xB z-#(Q6CElp9iROt~rL=9F7dZbi8@<+haDQEpE;nsP_V ze9A(~F_dE|$5HM~xhv)FlzUK)r<_2!59K7v{V4aRoI-gZ<-wGPP##7(o$?6EqbOsP zamotH1mz6M8p@fJb(9U1vnZP=TPWvH&Z9hz@&wA0C{Lz5mGX4TGb#TteoXl(<>!=NP<~1I73DXS-%@^0`6K1e zl)q9Q$a(3(l!s8ty`{``?}U`GauMaHkZug+`5u^~91b@O<4_9n4gQvOnh*H9fs82{kp_dvvbFx)bPy#eH={5=wKNB%B>+?~HCLhjGs z)sPMRJs4y0U@auDS@lp)HYl*1^8Q_6mFu`hEJk(*F%Mj57*xsdpepp-e0oR6fG z`%O9DfijOW0y!G4fagV&#gyYHcc$Exa(Bu-D92MypxlRYU&_gp2T+z$9z;2n@=(fY zl!sFuNqID78D%+TC1n+5HRUmswUqUgNyh?Lb6knX(>#~^6*+&7 z=W@Ry=Py#eMENr1tCX)(zDfBu<-3$0PV+B zZ{p&{fy#TTZQr6GK z{}am3D3?$!rCdh&HDww4V7acGvXZijvYPT3%38{L$|PkYWi#b$%DI%sQXWrvB4sP3 ztoutm7E)eFc`@aslz*lCJLMIW?UYwjUQ2mB<&Bg#QQks%8)XOOKPm5`yod5W$_FSP zqI`t%G0H`hPg6cmnWlV&@-@mgDBq%dhw?qj4=6h+|3&!;<=l&4XiK{=oDEXoCx z=Tf#&E~LDWvYqm3%4;dFr@WE!CdykVZ=>v>{3qpIl=o2HNBIEdLzItDrYIk$e1h^R z%4aB_qkMsKF=d+a70TBr-=KVp@*T?eC_kX=r2H4jXxs38_$}Y<9D1V^* ziSif9>6lZ>Sak&DQIs)CdDbLrZ}N;uq&!oSH8^>eB4y}*2KiYa_+oP@LVGf#e zbp1a*zWx7w^j__;Zl9cEt3G{-`u6R+S5BCC#N!U!rnuu<|NrfbJ(jNFxM~LpQbRXLT{|h-DRr~ZWBgZYakKaT-gxvD!p&s~R@}U9!kI2_0w|xDv2fhaD ziM`KY`kRs4dl6O+MLqC+$>kMcIZQuJZtq7JpGUrds(V&$3wz))yxB7T&5Bv)95dr;0$7v$TJTfGjk{|WN3rt-jCif!{&CT|oa`@=@g0KKlQt`DR@DVd-0g<3g`LINDA*<&^nwCQi#7;Pc_r z-s9menV;(MfZh3j5PGj(AqfTH%%9(R%6w0Lo_5;&6v95O^Atp-r{L!^Ry_rtiPrz) z=|@-2RN#O3bG6ft-hVXz$j;0589#of@W?~TTk4xz!uf@H1$mLt1%)l{yl6WT*0NQF z)UUqYx0!i~X-I$H3qE)Ci8>T^f3Xh>vO9h0cY322X&oA+rV8m_gm(mZ0lB@uXBSPx zk6zwJURR~QuOso3L(Y2PtvGxC&O!V+4o>Xb`+&yJ#E)M1l7_v{XZ(8n=q~J4I56sntV7Bn>vWvhF2zI?4AT2Xs6#HR?zk`Nrg}KPg`-ofVF;7FPsF(#r;9G%8PSq2ZoKyghmNc(wBrCw>!*U#L8PN4+3HUbaa2nvAD>r$oX{KB4@4PVD|l z9(q~%_T+bxN8eX2dwu0tOy2%^)SYk03GNc|RG0GC8SWZLpM=}~o$}q7KUjUb%1E!RH+EHiva=i}X9^n2v( zzwhCQ^*dzrh1^A`p zrOVa+t}@W!xS71IkH%*o@~6o=*HSLeR^<4AynP+z!+R81J~Q2$Z#D8oIO{B!cqWDU214Q~kAy`(F>soI%E{T;|tn<;;OuzHLqZ!J|j?^9pi z7jWf%pmLkA%`twE@}Jp1TxfP)ndmIY+MXO&oBkn_+_{zG&E)B6YDb<6$?>$=*>_)e zeyaP6eSc=>P}P@xZ*nXr_qJ61B-6DXZjdGZ;k3p__Rz^8?<}}-U%aMPYWy!|JmsASiBB?L=Yd#`8h)&?4neQ2$x- z=m{F1wV4kelcz6JeR&=$$JgYgt*U>kVsA|>P)PoGCo9iqzHLL^@i(;tC3h5(M^9D# z-fJp9*z_+~{Uf(l9wSdr(tKFS_|%b?o}+fIq5YGL7ij)ZrT*3A_(v1Ya9{NMay&#{ z#{R^{yLZUbF55uj<|=_;A@4u9`v0l&{S|wU8*flM)0p5_$&(LjxYiz*k*Ag`znyjlpx>+-jf|M`_srHNBj9K<7fN%S>)kmny%ky|9bNDuA0*I`>DrEBJ zbDE0{_A`-3KJoL*$V=y_{bQ+rGkN-C<##Y$De~4Y)y`b%ziE7#@)Gi;X8&tV*R|vW zF#bzEB)c>pUL+5bclFnjb^jL#emjweSfB5vemQvu$H5)R>&Q#@Qag*u7m!Df({#PT z{Jfex#r#CH-SH54_(awJjQQ{~xp(Ijr?)%xza(#;q~X?)_eX=0^rnwcZvDwf&muTCX0U{WHwY z0`2!MCcl)t^bze(%>JEbzgf$1XO3%6lSf&O!)T`qT-L8b37scfzdZm7u3rkYqaoz& zfp)hEd8qfE&hlkzY5*nVokwavFZ(X#m|*rtD}QHz@`K4ESMTS5rK_I2?H=W8GvC_C zOP|v8%6g3)|0EA@qP$kI_au3md=l=Jp- z#oou{-u_yzEIvPzx9+QUBD6DPxaLFWMCE^DJd4OX_E)|yE71Pr-a*P`pN|~1u!HeXf8rA=bcD^QW8>%yn9W{AgKdhrjeeQfk_1*sk z!Zn+dx8p>eKDuc^LX1-Rrx*B)wTS%397 zoxJRE&7XN37_KDm_(-|6yT`~|m-zGZUGn698vhR&?hoXleU%@{9-Nolnjn4|^ z&yM7!lU1Lo^Y%6U!&SeU_RGo3j!<4s`zMm8k5t}7{aelcQOf%=gWe-=Yfx_MTD>>Y zc$UpjK8pE2mb`=YS$iA2?@8`8s{RcOcdqG^%ic#hE+_A5R{e*`pE3P8$}7pgCr_WE zd?@p6@W#x~0ospQJIXhHy6Ve5Lpdgqx1Xi_W7VLA9+P|B;^V7W?n5fu$!Su`3&J4!?EAmjeKmLR9U|H%(`!Rl=Pu}`h4OjLz%5e~R z*KL0NX7cEjs{i=fs(-H8*--1-HW(M>xP?4)tLnF~UVTE|In!^yKNk8Vo^7|OejoIw za%@4KPN@D;+SwP}tyA%PZ)F@`%BUax!LR=p^7ch)e?0YXFnz{}e+ z?|M${9MASX05^IrpAS&|H)tnJ-cjw3&m{8Do__l?$y*2LJjc>2`|HGhDCFnzUX1YM zyBeQ=a~>$~uLy6QqV}!dmiJABx1FW+>Kw*%t<9CEPV~pKh`i$*<<^fKYWnNzJkadR z``j*`hby;v<^|-*r5exuSdQ}kh3KcgR6c1fjgY)^A-tp2AD>=f+W%SgD=?pyBOF#< zRC>wiw(v4h5E0qb+$Ej6C$xBcO6Lms_Y>rWo__atv0=eIwTJbZ`d&+p8ibIDtW zYdx`ab&z-M>F2MIrw&s)?M&A< zu=NN{SNkm0|3`0)=RV-lABNZ;vRJ%o>X$aF{s6}3FVqj`>kOew&3M;Qzih7RZ$|y+ z$y<+CK5PxW;4|{}9sK1!cx#PkS+T$VE{}be8%l-PFk#`nq{H;G6 zJW}Hs*~`yM$kR*wJWifk>d%K$$)n%;`E}&2-}(8|{X9Q^i#$9*xyAE0^3X&--(-}=Gkms|%k`{R zCFG%f{rZQKcOB~I$CH;HrQFhW9eL_}KVMAl-Jsms-4gQ9y?*;^Zm;o7{o?0482^jL ze-YcuMDpnE{`@?Kysg+@z6;3H`}@o5Zqwh%um2`_=iYw)D|!2Ye!lSz8vpdMe*1fo zM^5tVR~bLmuYVqS*#&<6JIGU4`1z~k$?N?5N3-*b=34>gPjaj~n)PAje(v1LaToH` z29td}K_1%O&(Aged47H`dGrCbZ}szS^3G@dd^ve|v7c|6r}0dt{d{-w_Vp*Ycploz z3GWE<4&LXMD)vq$4_~YCwD?>@9(vW!pCtF*Q*QP4LvUH&+koqP9A3P>SU{F}ns=ba z=ilu2!sKNKD1Vai*^@jnMfr1#|6$-VUY8x=Uw^0}pGdwY?Ho@#-gLEd82LryW#qSz z-$foeT=mZv5@ zyzOY^{Tc4jc z?L;0W-@*A}-Ge-IzS?<@yqr8qecUFyqlw%L@YBh=p4E6psedkc>Rh$|BI9|T=`-Ai zsQ)l|$1_?#<-J)s-Xt%(RPEH$&L`yI3zhdG|Hk+w%56P!IeGMPE#L3eyf?H^^SL#- zwtpn6!0(t034R;^L{}A%jbE+@zam&$U_HWg2S2Ckd zBJbq$dVBtIE_vAtYNvyC?lAtehHLHXaq`sKS}s>DOp_uV?(b z$iv&Jou_GM5GuU%pIy(Yo%_f)B#(Zpd|^NJ7)jpoobqd_zZ-ek^U6d0)nh;M&fT;= z+dhOD-W{pQu58n(|2h8zcNrg3d!3(P<{C>lpMQ*%eYgz z(^PjJa(}VzM_v}-3GzsQ&n1rr_}So6zODb#aOHbda{P_F<4fhIu>Rai9{o!BPt<>q zyzOdD*DizA&MV}dKdFAHV((w%k)M^%V?F$iyyI8p$1>dh#Tw7Za^-VrXLIuOK&`i@ zu)Lz=9n^oJpI)#(d226ik0TlXI`Yn~)Xq_~f0EhhtNNb|QjhbdKs#OJrSGeK zt0(=(X+Cs*pgcwWjg51^#J%KOleZtL{ne%9JCR4tt8ja^s1N4FfadHaXTr$6=Xuqa?-R;#9eMlOs(-O!FGb!tOnHp_P4duS<+fj9DY*3iN%q@e#%Bffz0Wn= zI~o6eJ8S-weyZH&4{MVr_fq@A*&h~@w_U3I^EEX>hm)tdpJX=Y;U}2=d8%K^aNEf} z?ibpY>AHtJLcS&WbL3^@8c=b@1p5SQGYXryBT@sSDN0-nLowkt=}rY zgyk}sJp6<5Hrk1icl@aQaq?Ma=V#^ip2!(y=U3%J253C5AWyEN_3iw@%5No)j!8yxR@Th<7o0Sa?@W(-WK3bk%t50;~V7df%@Kh`+!m(iTvago`fzV)B~Adf6oJDc`W{in!FUs8S??R1i- z$#v;&;t1*-17Lmry0 z{0{P8$ve;0aeq_tHO4a^4p9A<2dc*wjvH)ojrQsvdOb251+_itm6cE@GpZM&*|s;~0f z$V(Z|R>j_v;4VH~ue+V&@@wRg!5YtZX#Xqn&Nq}-(a!JWp&`mY9Ht%{Oi=q>TPsfw zP@YfTzPEC8zwVewUV5LF%Nyjf+$Ztu+DG;6eVinDC+)mUeOV3?{q`}c{|j$iW!t~- z(#2}uo|8Uh{6(#|@;zWV790PK^_lJEP4Z-y@_DN6$?dAx_g+!HBl!>HZ5Jrdi_inZ zw@cSIT0ak9KFjTuv-6wA)8@~k$lL#|-1fmvF#VXO_uhVL|6uacH`E50p3dQ4iVXD(NkorhGp0 zp)a~Amk;DxtMQ#l@~-KsZ}(FZ$dlX$+rV&Vkf$oOXR~_MYWlCKo$nd$W#rL!7*E!R z>rFqQ-0pX7Cy!JqKZJJfBky=Xxjny@;Y{Y$_Z_JzV)b<0yUPcTv-ynwr?k^`vf8;Y ztRBm0r-bYOW+wzSu@n7P>(y-9k>=p|fm#oLVLbOFFMU|;AIox)Y!v-;N_ncc8aj@= zZISXa+CQ7Ti+nfgUrQc&O7(wcxDOiVdTbNzNcSS)woTIV`ilH#^7M0RXCdRcA(A8d z9emI0Me2_xFMCP#A0?ONIMGk?y{99|4>P{UevVv3-bC&lqw(2euzJWa=j^aQOj7@P z^6*j(cRb^Hm+60{e7`mH0@+qAc2aDoEzGyK$)o>L{lC-xGV)IH=m7OtX?9rdLz&N8 zA==LVPiki;+LzbagnPd#e}wuIO#e6K@;`dyIE1`(00P4Cg8PgApF$oUsC@ta>LIsj zE`QchZu^tZCofx9`TMkUBYAX~^51ADWqf1haq7QC-Vxy6l6P&U`i*Pq1^rR1?l~IU z-3GL?0eNeHk0Ngm@Lj-Nx{gx&3Fh0u#;cUu`|LI3;hmJr_vPh~?P_ADwOYB=w~Nir zcFM13!@kAre6Q_#uA1@W@u=A8{7yM;E8Ovo=^vwZTFBR&toq>^fY7l?JP%&PX~GGbk*OHcAg;b zr2f{l^D4QwK=ntlTx8pfEyBCbQf}c6#PmaWG@w6>JaV?`FJidckSD2c zcE+0iIezJmY z5}CLNmp03atrr&@^-K7@ta>Zqt0TcAX#(@&553+Bw1O_g6cU8P9Xclhn6<;~!?9 z-0Dfn>~J1#{C)Dy&DFle=X>&SSh>x|);dtrTSl(Fo);$XAm5hpA4?u$xVA1anY@(T z>Peiull5mh?aU%?Wxk<%cgLyZQF6QQzKFamz;7ZCov-=72kksYUizz+yX|lM#OzRC zeeu5pjQ^%&OWGNMf^+Sc`sha7u@kwsP{SQXeh_(td@6Z0c^mm0@)OO@TAK3<$*&?W zThq^joB`CCGVpC zyR5ed7~fU(t^Lj-_kLix*gT57ZIf3zd ze&f$5-bShuW zdesjdrsPj&AU^Apmo8QP;nXiC_r6t*X_7mRGJW#j$xkx=o$BArd_JGNjs1p|??1^? zp5{+)wxbuw(*u-WNBiHBw+>XkkbM0^HC^E~{Cqd^RDf5Jw+H%@1?15|YUftk?;!6A z@Yl&Z)=~YVslUYRkT;P3+w?=KZ~f=6!&oll8&ZE1xi>`hPv(8)uHTg8-67tC1%73B#gUOQ@D7W>x<4ylQ@oSZTyQUgSkcU22Ud{S^y7Ab4PSN7uP99mQ z`M*8${{`~)UYL;JC}Vs4nLPC`wQu9>z{543A(^n@*noD%k(d6g`c^Lcke98c_K%`| z6M1S~aDrdOYocUPqq3M!D7JznYzU{dR67k3OW_;`xZ# zxl_57*IVQrf7gD->ceN`U61d_pxy5rTdD#$iS8x06R({rXQD@A9YXbMjPwf4D!Ar%&{25JNx{Ke=&gAKSeme(} zcQQZCP7`@_zTeKtuOB!2L;d>4k*C*DZt=gIyt72P#s6mV z)G)vPi^gB@>%U9xZK2%K^&@%drhY!Sg7smf@?}i#*5sYr`So`vFU|MsA41+bLb;`D z4td)Y<(96~$vd|7>)$}$wTEB-ZnKl8+|ugnh(jt z{NZj(?lC^gm_NIcNB2>E;|G(sAK>RTW`Ba8pG4mIt8$CyIpiICD!26BK;E^l-_FD2 zk%@l&H_21&Ia?6LmlZWJi2oB4Kd&tvqKTnglRr>i-^5`9!4>rE{tJ3tQPW6YoE_v%* zC zdH5AS--SH6*v}6l554Q>)#PO_DYtM>G5t6F{O{zYule~sSuaKvg z`t2+;JBRr7e>45BRo}{Yvl`8x_7%#PF`tXb+qO|V#`h)fs`uNUMxI(%x#dHn+4;BH zxB74;dB;H2xA-g~Pw(uv|FZG1e*0gNw{EN4>~C-k(^a6{+WSuAspI|jCmNrk+|qkA zd6@4tTe;MdCr|X-Io9+i`t7ulx9y_b;`2}Pt~S4&6nW{se!iHzqttKz6Y@yF&UfUY z{Z!w|WzbB`pU@=b7N0!wu3CS%yO4*c`@=nmyz?Nx{aW&}L;d^=@{aTU_Aexl9PPJ% z6M6Coznv$@!!bYqfV?xV-0D^DTJ{4c`0Z~%-chIA@?j@(kK?SB`$Y1vJebC@jP2!U zv(up5>fzbsp)>sYSCJ=Mm0P(yNZz`@um3W6>Kf%1pAX5sGyQgcHT}Q%`MPzQ56N@< ze6;a}%9k-+es)6-21zqKTjUL zOS##3o4oZ}M$h&Uv^M3W351ltEw|H(&o_0`6c9C)VFxvLEiqN>MvkiQL<(ALqk%#&G-T39?De7B(-bvo^l|S4U z$&;g}xcHl$cg@cC{&2rAJ3lKo`zy&?e^74m-y*5`)4sxQXGi0|D7So@L|(SsZ)YaC z*XICVJm-;@u2g-C&w1ogPxa0IKgd(Vm7D&f#%bT;{}Fj|BY(Kxkaw)_x4*_LO>fy| ze*2q~M>bY&_V*w!jri?PF}}IqehqnOOXb#YpF`}O3d`}yt6C-2*X}+g$sk;i}nAtyE_3iol;3mzV(2>fmeDldu z4SxH(lXqO8-174f^46rE&m{NGQ*QZuI(gSQ%5C4p4dm&&m0No6CQm)=xAPKtn$iiMx|`ec`u%2zhHtxuv&(JhFYM zi=RE``kUF84MjNYIoDI<$ye2m_7eDR19@na>f3X!fh`*U=pxm(_O%0figv7BOegRB zK=ti8<_YB9PyYB{V){#!+jGn($jkcq>*p)v(Uq#dZhx0N?>q8PiN@cayKOd`@!wPV z8XK$rB=QjVaas8uPVN<{e(%1je>Qn)bB&Lc`&HzTJU_peJUL1E$!f++lXryu`k#?| zrOK^*y*Z5kPJX@-dFk$cI}!4-EtOk&O(plZPsZYZjM+_B~$g5A}{8K9!nT(VhNKr$5~58tvEL7#v?Y@Rsa; zaMl2R~BP z|9SGr#Hb@yE^m;x?WO#6>VHHY+EY2EsqR>2_P17^B>$PbY;WbA5a*X|Ffl@eRE&zi)h_ z^Yy&_z`LstO^zenaZ#t);r`-%CC7P^BhGhx9nUL@I%?_qyWv?@n?mmCEcO2=Rq;~B6_u?M(_oaUO{;K~X?H@v3wyAQ9e~dgbS-Iu^G2oI9 z_J5yg#+yz3Qr71U=y5uEay_;4ck=VdqXo(>+`p2i8%mw#uwHt>mE^5EsJ``AH<5R8 zoU-xjZpZc7+`ljNz(1y)=xDV+hvUfCdZ5!8movQg(%5iEsZ4DxC&MVX}-Awh*V_e@OZ{vGa`;&i09^yD+{o%j!rN0`4Mz40L z?tN?aIS&~@kKf5VqN@KM`5LEn-;aeI$M+w0RsCYtx9!PWcT#Tm#l_?ij*nK3UPWVb zeqMtYi^a=pV$Jax0-Bmzs;crTyvjslVn$6XO-B39W_S2qur zYE=2&&SsTy%s zg^X`qqOPG4fzN7**Jfa>G6A)Qd9mh1T{0H0t!=1?BT+84ql*j1j?HLVk{L!ozcSt& z&!}2Ff;t*hMy+{XGJ&*KHN?u}P1$s1aYaL87RO>)kA^D0k>+?gHuPA=Sn#zCGiD$g zl8p@&tN2wk)Hf&QHfMNDGo5&JQOUTBS9Kzun={>-3#7DLop@7IqOrNAp*~g>uc>Wm zO!#A#L}@n1Do_N7vnvLuW+I`!ff7|%KCd}9?NSSaMXy0;jOl3}!b>#P$7@GNGCs+8 zeN8sATIy@0R@b4vCMq*JtViXD<4mnF-FsCgTy@HLx--=ChPqg;i9hnsLzYw^oSGT+ z5|LO#RaH}>IVaBX#zcKQ5Rn{~=Pr#^H8jqNH&(`4km{U^{e_#2i?dnPm`KEG<4w&y zL^!L2v<0qZy9!X32d;tnsz^mca$c;dwx%L6x}d0NOt$W6I$Z54M^}`s4>=x*`kb;s zRh8Bst7$^Zb{$eC26gb8IlDS0O*K|IuRdOvQ`F*Fk*KXjGpJ0=HSZYOLsK*2G^V7) zXGhDjJ0msqQp+_G-TBXGY?x!_eI^?dxNtT)#BO5d3ZgrY+J=UiEy-1`K@Y4}Vv?>I ziQIOc>r+vkKpmDSWuoH06g8~}A4Yd{@z`w6xDG1Y%$pnI$;{RMisqWW*33ku*4-W} zrC(RFVs!qWGK(nI9E%#&jdBLXvhf-{Zrqp*cUTQ%nCjB%xN*f952q{<%Mev{aUC}< zJBFa$qe>;`W%LZh>d`GW%uY1c#*;~mYCeTPG(`2-L}PdU9gB>{XdxLZ94ag7 zvgJ`hR-4E+Dr3zJ!49;gvSxN&LuGzJwyh!d5O8nL`8GpG&>8hAwMtw;9b4w$AB|efY7q6Uo%II-piaf2E zO?hs@>AAUHJ$fj+1|wNryctu38JM)qZN??VUemn#idaQ;ygsi9$sJuVW=v5@<^mL7 zy_-{HrbDVG2H(1^qp=ky-JIZBQxh-7Ci{uC4yW-E46 zHD*;aGoyiN9B<5OxM`kbS2MFqe$Ub0we`V3EHz^zURE!oRG&yx zHpLK#Mrri8vH2N47ssaNcymh==H-p>E66dC6Ou-pJ#Hqv!W`u)Pa24{<2*ORw;C+f z4Mkg$9YL6DHa1&1p*m*bg<6dIj((*%-ZT>zkMsH1T-K>zB+J%*nQUYBYGG?*fJVe? zXhP$xY-nlLU+B5(w5p>WRia^|aI)o~%IL^l`gD-bR@to85pj%97@%D}&1*s(j+M_F zT~IuxaGV4q)4oI#uy&6X4Y--}kcWAV4Yjr9xWmK9BY)j3pv`!?@y|>oz2<5ea`KW5 z4KjJF$<9|?Gh%ZoF3v}kGMz*PE|H-Nx4F2*MSn52Fn_G)T6ms9jX*XOpPx2gWLIVw z$O)@3KOad&hPaN)=TS5+JIv*J6yz6;b(w$}Q^icVsm%o3O_F?-1U*qbZkm##3k%1L z$lq zh-bEHU}XZVV)p35l9Dmm{w}wREs8{Ni?V8i%4>>O$Vg$0G# zO4)5ESBSwQo6S9_L?YQbOchtTi!oehnp*eQjwysn4`pxnsNM80=4I`e#sEGWpC z)cd{iONv%WF|W!mEGqKXObaDj{fu-QZb9+b$XH)dn7UQQ9-Ej}0@N{1yj(SkRn^94 zpo)xh%?^EggHC4LuPWbsuL9jOCZINWX9u=;c~ka8*2se8!5+AzB)>R2Rjg0U;Uu@A zsYcd>nno9ta7=P{5LoG`!Sq?iPgRs(xMfddrwENTb+IPQn6vH5R}vU%Arg{ zy9;sL7__1uXW^NI%o9(Ma6`Q$n_tS4|r-`p@WQIEUfM)b27BXf<#D`&^iJ7GEm z6`zl+ezd?8ZYSN+6BY_ulKxQjzNDLLb3J15Dy+2mOe41CwQ)>MYbs??i_kkii$)M<%6n&CR$WdobB9Jquv;=pCS5V zA|?Jz@3ux|A`r;`LN?BxDn#)(j5gL$`mPhCeyb$-8Bld+lDF>U_BPU1MQbV;ik7qI*D>j1+jckg)R`c<+!BD-)aNu znuwm&(cWa;NLME@@yIrs+*%YciqxQSR%3?Lw3?U%nzDao1z^RKFz8_RY= zXbh;zu3hFv4Ms9?jx{7@%)l&Cn!_sgn2M}A8(wVHu31KfoPJ27)l+>NTU6rj>RoAT z59Ujda!JCf<)(i6@L-ih(zBs*UN`pURAZ{&!zDSCVf7 zS&tfpm@4L$NidLXEP6CiQsOtkEUfA&VNLD{Oi#Xu03P1e!W*}9ZivbjdJ}HEa~f@r zF)GBGW^ST1MpCh|c^$ksh8tu&#jJF74>Okn+`l;+x!ny$Q;wCZzq~LKDe5j$D)3Mc z4_)Iecs96d1RJZH+(KJU4GRWi6RWYZ(y*$9ra3ju71f$b>qT>?I#@;RVg9sA@~u#N zjKNskTXNPh zbEk^__!X2CVMV6fgji;mj4AGZVXm-HR&y+)yT4f}9GBl+4};2>z+E$XDtY9H@!3E2 zXKHL#srxvtk{B#5u39fFG#8G=*Po3=_&1B@BR9}F&#m;iJ=(;We737#NoI1YxTmtt zXpF@j_^J_Xsh7nf3}Vtq@dPZ{jgC$$v!iA(5`nB3SBzV;RYOAOBMVsB9ACYe!bpAu zt6ZzORI7n^Ffe#$r-fQ1l0xizP5>FsV8#N@6`Hf!hq+DXtm^_W%d= z$W@qb{C!dLhG8vuHLHRJ<4OYkEaqXFt2!@5-eUTejgC_H2woN!FuKSl9gc&(EXute z4^}dnrM$?#jHPD5`~em#jNN(WNTrhq}X318W*>uT`Bun+x*M)AB48I*_Mvc2wMOjm+&njqyo*H5lrBX&16NtG{DT|%Tt&?t$!yuC z)?JNKKY2R4+Judfad=hBE%kBP9hR9$2Wvr~;g*am;Mf#Yv<$?mK69r>jWPLuP~{57 z*%;}o*jxdKt!Cn4u|TcuVaDQ5P+?)AbsE9w1{KGQ?O`P^pg4A%&1-`eEpjE1f*wp| zG)ipb=*h;oe5?YjuE5cU74@JIi14`Lp2{PrIJT#ICBGt`6cqeXGwU%6f~E?4%B!H_ zm_MkQMfvgsXLVUtl%H9x_0>ChM*hb)9C)Tsu=;vkR8W}TL%swOgH_xfrjY?f+(mWY zEfy8#kNbnz6c)(S(A7-aii(1(ychlQ3ZQM zx-AsulZ-omq%p>NRbNiZ0GiW9WEVuxVz6rzRymW%`swCdSy03B?Pxsd=kS%)&^5-E z=Bg6k+PRlYba^}&Irj{~?Q#jk+C2%FQ;k)BecToBmaZt#EDu<)Us;y!^Ri@B)ykR~ zc>a^4sUV;z&kXFbPcWX&&)r7UU~0)_$7}{TZ=Kb3du0L&Hbo0uCA)*VztTlgraRr5 z?5?C-H0oRG%6o{RdgVE_x;UnrO-*r|5(lD{tBUH4Z3oyw&Bofjs_bN!9Y3Qv@N}=` z!p3_K6*AYwHr4D?VD3+m{Aj7ST`KM=wtq7>!Xd@VQ1{nouFhH)v$I+fE%NdK>ViD| zb{mYb9*E^YJn6}?B-`m?xWJ*jI;^bCjm>DmMsfw1t5?S6#9^g7^U0Q`>Kr}Y`H^d> zrCwIFTnyZ{*sKxThLH(dclTwZjvXMGN5QsFPm&{#ITG&CYEya6MlszqmdkVZG2x+T z=2ZaME(XWBHTFPN)k-q5yU41zYN!t(now!P59?ktVythuy7~}T?-ZeedLzDYa)Dmy z`cITabI=e2%35YvQoY=J1Y!a=;PPZXFW!_fA=b;W4Hr)U%Cj5jTq`kc-Ml7Kml_nH zY~NvsxPP3W)AJmQW;+Oo7?fT| zU4Lp^yfJuY;-(1+MlMiN+{~T1D2ZJ=xux9H5XT^vWeHSXt@JXy(U?^i=joO=T#mW> zPE26%5*IT=6x<00SlQULT5G5wfykml;PIX|0LcTMOI_m6SY?gdtcR+PE~y5?lMKSO z@n$Rn*P&flOtTf+s&yuxWPi8CSJmWY4>mgqRiWE_=@hwKg5`$M`EK!^O%0K6!Cn|} zS$@U~7@3|3O}W`kp}M=?mR~YYFR`N~C+5-^b!UM^Qp3q44VhtC-h_Qxcy?3YQi~0r zjdhs4x-kT?EY#lAz1ZR25=b^@TeiM_mwOfVhgfLYbW2{iGi=vH1&Lwql3&cer+dHL zUJY;f`K~MQhmiFvEWr5a3*G0JEv!IXaNU^ITvwd&lL}jz?EtO8N)nt`UX_f#IbAX- z6fqpTjeB@mD51abU>*-&T=f?pe<0k&Ed3%XzYYUA876huEyHE5!URkQx3v&CE$vhUdAhRXCMF%fd!jY*6Jj+0g{F zrCtVXR*)E;LSiGl+j@^0hs8Zcy)l6{h%Vmx5^0v$6V_0Rse&84ExP)KXg2zI4HJem z8@O4P!;#x7?Jna0jya4qVl33lAn&Fsz@EN33o^9Ctn0?e#r8`zn^z4GbWz20;f?s=%hoLTpIRK0@nub+w=v>&^b{f>Km< z$>w=}ie6*}-W2g|dBqevUQq*0i$!-kI}3});o*Z`$Gy-onRm(iV59Sk@FIlY&v?E+ zEcTks-B3T0+P5l+VNCXiBkQ;bAN%gDlF8ggUd~~iljgyZ%g`TH!#wVz%c=N(`Vu^~ z{gahF6G}Gfe>xQG!u?M}$>zwP4h5C&KMe(2Z~xOvu%Y)qy#$MQ|LG;zW&fXEBJJ`& zzUF^W{&J$j{aTbPT?`9O*D+Vrubl z=Wg<7Gso=g!1s2c4L31)OVDnR-4EP2ql>J<`y&{jn;fdIsm*y|LwYZVW%lKM`0v@} z1#Fpj9Rm3Vn%HVAkKtMjkJ#(gQeTPH zsS@;{^ufm$uw%FxuUnMhMT1=EUhj6Vn~0w#7N#<<zmv4beOfM74I3sSLwGglZ>sRj~nDZ!NrO=HZrW{ zDz%n*EBcQ$wL~&wJ+lI7;{y}!iVT3Nv)PRa<*S&$%bV^~6$rPv1`W&N9Is7Q$Ae}RW^82Mvcn9y z0!uaa(w$pitEg_NpErc0J$1NRo@ zfjtgbXK0QK?)H5zd7y!N7}IY`;(<~2RO(Z``;H3MK$@#zMP5}jW#^G8(ACUGiwf}! zRbH&aJ{3GKZ_2BZy^!t)Y@s*6*G05G2$RjlSkrFO8-dCi+!^{p)VD;CQSvE<%*%U4 zMMW~sa6Zf0#rB8C1SMG`?#-ZJF6&LJg@dY+-I*4Y) zd#Z~#=*Y0xsM}4Xgz+60yfA{hw@e_l@nh%NJXaeeavcwHyH#-Cfe?S(+FIOm4QNQB zfP2{P6!>b6Xk;@lXDdVjzA2GYCFSEfa_?lzHkJ~Xr5KLfxRV*J^l?J&tMbryD{OA1 zyh#-6aWf>z%z1}aD>B!ypu%_N-ICPkf_ywm$qanCs>_*n%5+hDYRC2Ny7I0o z2HINi&=BvcTjX-a1iUIy9KpbBhE(dMG9fVTDy9F-wJ!6|=3YQa;vYbw>ql5ez?V~C z3*9Y-irCC}V-lrp#`RrYJHrdlwep4n#^;O%S>{~Yx_d#(E^_hLk)X6T38no2qN7va92Gg$h%#rfTdf#jAL$C_8VXdAi<$rB6JwYwDin z$!oT*dSb=L(&4*KUgA~-SC*2{Ox!d!?!{4F8}QP#O_w>Wud<3};nYax<@N@#rWU+K z`2V$b{xEhPMF4*x5pjYDCIKlDi7>*@-Q(VS-}_hP?8J#JIe(NmB2*vedw2FJw(oN9 zlGqKTprJr=M?*tJ8VZCvB2iG$MS>zwr9rZ&OoJi{%x`vgc4xou+xM>EB>R2e?#|Ba z?(FRD?96;9{m}<)v%L6)ME0f24~ax0E+{t2iO*&_6LAMOBUZr)D~(f|Q1(OovN^1H zJsjHKkRk7)Vj>rPy)VqG#PuQA%aqye(u}@tiANJVVxJCEvh*I-cTl1Fl*RTP)Xckd zTD+);y&(Lf&5em}L~F-|g*@TJRu-cUCwQjz7s=8FjCGn_FPE==Wa$3js%l0f>Rpec@|l~? zF~%7o`HqVuqGjBh`5V3hK z2!Mx{HH8^YHfM3US0pmX&24{I8R~bX#gkkvFy%VSrW51?8-aHXC^!DcQFwQ(EEVD6 zx;KKj;3s_`ww4>!6mz>+SJ+WCPAOw?yVkWL-*ho63bsyTXox`4H1B|DB1wh(lI%fw5~vza7@wzoRR$Ip=7 z31Gg2N8xMtDX|xPqS&0XXr(3P2gSTbR(N5FQW4=mS1#}oC$(cZg40dCw~*ry=Nlp` zi09F=AFm|ju;n*a{&vrmcjux|Jo)If=eQh0T(pg9JR!u2pc18afLu4DC!2WM1LIC; zIpT-=9 zm`eeLkh_4C6`_`RUZ?aM`D;Az%5sLN!`CF0bzM#EwJRl=zPW-Gx%IB_N0o|evJlk4 zF{)*kYy-qh7+UIK=xn%D8lQ7WZdWN2mmSp@d^dXDkGapGPkO#ARNk*(^sa&%GDXEv4XTHaZXl7 z&}El`a!1!l(w$OPZuoawMB2e;JZnZ<6W#Z-J^+R*dTG@8z_L67y$ z26!AOp_xFysg_&YR>dK_oR!*CKEQQSQQ>S&Cx*93IR54)I6&S7bI$wc4~#hUQPE1U!VX->schz8%N{rDKx@ssf)4+ znvd?W8K-uou^~!glIi(at_#fu1&Gc>_gs#tD;)GF4{fnLdgBtM6E-{DZI~p1Z`v?5 z;WP6#2&cA3L#!Z6%uEr>i>8boE zSa4+6pd+>-_10j(GTG`A+SpmvEM+4mnAz2seFdf*$f%Aw(eHJRBQ&I(Y!MROayq%> z&B+cx8G14C=teY317o_N0!N$NYPQ;GZ!{Re2&vP>tfd?4n5j_6L7`B{!M9Lz8eLMM z?J;Oe&f+Rf2NTr%u{{2tiI}`%40B5THZ4s-!YHNcvhg8&UqqYwdWd7K5@S+jlv^V( zw9$9+x-oUt@*4yhG0RupBo|a%yRmzlgva7Ab#;zrWIiU-$T7XRX2}MDU5Idxjy(YF z5*ni2a`r%)DrE{gAHoxLEis7Ft~#e_2V#0&)az~hGsAs(!>uME&zTwX=w^;-F)LaW zEnM|XZAaQ!%*NM3&CQ{^G>~&FKU!@W4Z0S7{Kt)e#l7C_z$_IbIfEzzts7`IQfVht zAd>A;BvK+kvu4KXGsKwiq@2|URpL}3dsZ$GNNd~PAyE_BJvBAwv(X53itdo)4s0xdM*8lUgvw@Tj{^a$<``v@$tU-&Q8p zeAX6rCvl1zEND{{+N6$y$!}i6)Cx%}#kkx~v?@#cIN;XCu2EaZF?~cZ;EoX*%f7d> zf5&t7?1h}tK9ulDYo-wrGWHeW<_BhH#f5!Z=2MTM7b)<@j7SLD!(8{zxB-Zxh3=(8 z+;Vtd9TsK8w^$-~Z;a@&f~es`9%~ki`4lsiL=Ob%TWZ^UW*n=sF=Qofb1orO*7Yx6 zUW$0Q`V-&Ct}&!0&h5?lQf*3)u38eP(aed86KE2+mi`KzL)z9V-!`m0%7HJ#&YN0{ zT8m3sHqmXGr{)7qYc?1V+_8qCr7EmpvgCC$A27l0pJvO=Cq%SJBT7>N8nt_5Pgqw> zB*Se4x3>~EJG+?S79h!ficgbN!`fXeE_958&5SqYx4Zgodz+yc>8u=Y?u^VFLqiBOql8%5HIjeQA#i02LqG`J zRkx9qgKZ5#OJt1As*xCYERE3KbwM25U~jOwN6dCqQc&Qbr?ER!I|N+Y#3`2$qbpAW zzA&upXyQU=AU6xzoQeZ`YRo#m8qd(5Ys<5_#m1<6N6W623n_(h6F6IVl0nmCR8pH^ zm1F`J7aBepTkXaguANK_QCUMUfHL#Uj>)uI-}@*U%ob%05qQT%XP&P^w3lR7Bu zO;D{Q-Kr@sdVcM7+d$dEb}xhEsE#{}r&WyJDRx25NQAGJ?jJ7qarpDg*9pOiWF$7Y zzJ6<9q;z*%pb%4);2W4=60JgP3)k2j82g0&QmmiF*8Bu;1a1J$hyW2QFf>gIPKv?7 zodiQ`Q92<84|gIAO__UA3=S?~&_N?2(6X+@NNo2u?q-@y9C_T$`1)2zP}A!hSOoI% z?U&y?_v(fE!fP)9TEFz8*Ur6q=|z%XdF}1`!o`q#@ufF=^(&WOKY#9W{rbx+4`CNmZ@vzWLj^Wfi$?t} zvJO_Hdnx*b_akt|r8iSEQA8i|~h)B?y zkXG@LO67CvDZxODVk(Q6Hl0@ERKN$0#UL`Z!geZ}3_dXqf9x#xuJ1_)hL64;@5`{9 z73ZMcBR4{~@vK{1-&?v;f#gIT$`vIPErCAzwHZ2^E%xiu25q^p#> zL8JHGXd&9RZ#FA%or`WoF>%g_kB)LzfK7v@vPkG7XAQOeZB|Q99m)6V!i?!T0&3^X zkQL1cmEa^lD=~j6QCwj(m9*ge87v4R>H3- zSq9?TO%^|gKqhpUdK`^Fy*vn!FjBf>#~WgG8nH`JzV~~)@Ag4Ehj(HQ%9;Ao>r^hc zu>^%#^mdk4x6d-@Sw}F?3!Gzju9q23%!;~t$G2IGy<~zq%VeAn516rcY9bv)N-Xno zFw>STiLC1N;s(~*POymaEaGJcd$u{5!yL^(I`0MO-mO({`oCur}t?pw{TSE{)vTum_mOM1CN**Q}JbJ;97#{VF677US zu;R@s%L>4QWvSt7PrM?09VQA_Tl&ahRv)o)jE{*ZwyQC9+LJXmqo<37w{K@f>iWv7 zFNU>O$rsP7ScqZ0cI|_+gk+AW((rkyZp$h;(uX3!We#f^PAWmhFM^EslMVf|nET%n zAAx18%#IHCN3oLBCmgMMxB=WmPs8u=k^g1R`zKrlcaIKx{aXaj>m%I>?vld19e20O9HI#&OSo z@UAb8X1%^!HTl-8Tci8C;t3+(%DcV*R^NL4V`i#{^I_0DoIZyG6u1wlk^lM*jjz%{ zhVSL|{ttiOqK-%S_oYzR8;@?er#Bw;3D1Re{gJZv@4LJ_&Oe1e4Dg2m{){cm^Qp!= z1DwI%4e)mZ{MknN#~SaK8u0f5{JjGHCj1Y9|M$a(2TK2E0sgZ9ukHVGz}uvMoAj^n zPvO4^@LvS@ABSJ*qeu9s@XzurfBXECA`1V`<6FG-dhZATe>j~$p5c_KlS96gE;l12R!sYZj3DbA-VSp9;8pTzS{x* zc7Wdu1r>j@{l5(G=R>)V1N_GU{@Kuf0xx~mg#R^ZLYLOxJH5plf2aMA@VEVIj{mR7 zwD4yF{FwlMMjI9?3CUW0?>7zjt*5tK$*re7z)`Ib{yl%|^?pmT#eeU;H(bGc@A;Bn z^0DmUncfcL+r^vVPrw1PY>F;>_e;+BN z|83Iq6u?4@(*I!L;s1HX!{5!pffK|=TGpM_M6II5tB;0FX{ik_*V~q?q@bN;nmcy^UwwV^LM`Kiae);27tzE?|<<& B9g6?} diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index c0c7396a7..0b77ad511 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -2,39 +2,7 @@ package test_core_thread import "core:testing" import "core:thread" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - poly_data_test(t) - - if TEST_fail > 0 { - os.exit(1) - } -} +import "base:intrinsics" @(test) poly_data_test :: proc(_t: ^testing.T) { @@ -46,7 +14,7 @@ poly_data_test :: proc(_t: ^testing.T) { b: [MAX]byte = 8 t1 := thread.create_and_start_with_poly_data(b, proc(b: [MAX]byte) { b_expect: [MAX]byte = 8 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") }) defer free(t1) @@ -55,8 +23,8 @@ poly_data_test :: proc(_t: ^testing.T) { t2 := thread.create_and_start_with_poly_data2(b1, b2, proc(b: [3]uintptr, b2: [MAX / 2]byte) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") }) defer free(t2) @@ -64,21 +32,21 @@ poly_data_test :: proc(_t: ^testing.T) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") - expect(poly_data_test_t, b3 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b3 == 333, "thread poly data not correct") }) defer free(t3) t4 := thread.create_and_start_with_poly_data4(uintptr(111), b1, uintptr(333), u8(5), proc(n: uintptr, b: [3]uintptr, n2: uintptr, n4: u8) { b_expect: [3]uintptr = 1 - expect(poly_data_test_t, n == 111, "thread poly data not correct") - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, n2 == 333, "thread poly data not correct") - expect(poly_data_test_t, n4 == 5, "thread poly data not correct") + testing.expect(poly_data_test_t, n == 111, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, n2 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, n4 == 5, "thread poly data not correct") }) defer free(t4) thread.join_multiple(t1, t2, t3, t4) -} +} \ No newline at end of file From 62b7d8de9732eef542dcb878b089b30972cb8afc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 16:32:12 +0200 Subject: [PATCH 068/151] Port `tests\core\net` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/net/test_core_net.odin | 276 +++++++++--------------------- 3 files changed, 85 insertions(+), 195 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4e13733b0..9b12f5d76 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -81,7 +81,7 @@ noise_test: $(ODIN) test math/noise $(COMMON) -out:test_noise net_test: - $(ODIN) run net $(COMMON) -out:test_core_net + $(ODIN) test net $(COMMON) -out:test_core_net os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index accf0808a..3e67b6e13 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -81,7 +81,7 @@ echo --- echo --- echo Running core:net echo --- -%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b +%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b echo --- echo Running core:odin tests diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 9df03414c..3cbb7fa34 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -11,71 +11,12 @@ package test_core_net import "core:testing" -import "core:mem" -import "core:fmt" import "core:net" import "core:strconv" import "core:sync" import "core:time" import "core:thread" -import "core:os" - -_, _ :: time, thread - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -_tracking_allocator := mem.Tracking_Allocator{} - -print_tracking_allocator_report :: proc() { - for _, leak in _tracking_allocator.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - - for bf in _tracking_allocator.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory) - } -} - -main :: proc() { - mem.tracking_allocator_init(&_tracking_allocator, context.allocator) - context.allocator = mem.tracking_allocator(&_tracking_allocator) - - address_parsing_test(t) - - tcp_tests(t) - - split_url_test(t) - join_url_test(t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - - print_tracking_allocator_report() - - if TEST_fail > 0 { - os.exit(1) - } -} +import "core:fmt" @test address_parsing_test :: proc(t: ^testing.T) { @@ -89,127 +30,66 @@ address_parsing_test :: proc(t: ^testing.T) { } valid := len(vector.binstr) > 0 - - fmt.printf("%v %v\n", kind, vector.input) - - msg := "-set a proper message-" switch vector.family { case .IP4, .IP4_Alt: - /* - Does `net.parse_ip4_address` think we parsed the address properly? - */ + // Does `net.parse_ip4_address` think we parsed the address properly? non_decimal := vector.family == .IP4_Alt + any_addr := net.parse_address(vector.input, non_decimal) + parsed_ok := any_addr != nil + parsed: net.IP4_Address - any_addr := net.parse_address(vector.input, non_decimal) - parsed_ok := any_addr != nil - parsed: net.IP4_Address - - /* - Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. - */ + // Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. switch addr in any_addr { case net.IP4_Address: parsed = addr case net.IP6_Address: parsed_ok = false - msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr) - expect(t, false, msg) + testing.expectf(t, false, "parse_address mistook %v as IPv6 address %04x", vector.input, addr) } if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } case .IP6: - /* - Do we parse the address properly? - */ + // Do we parse the address properly? parsed, parsed_ok := net.parse_ip6_address(vector.input) if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } } } } -address_to_binstr :: proc(address: net.Address) -> (binstr: string) { - switch t in address { - case net.IP4_Address: - b := transmute(u32be)t - return fmt.tprintf("%08x", b) - case net.IP6_Address: - b := transmute(u128be)t - return fmt.tprintf("%32x", b) - case: - return "" - } - unreachable() -} - -binstr_to_address :: proc(binstr: string) -> (address: net.Address) { - switch len(binstr) { - case 8: // IPv4 - a, ok := strconv.parse_u64_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u32be(a) - return net.IP4_Address(transmute([4]u8)ipv4) - - - case 32: // IPv6 - a, ok := strconv.parse_u128_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u128be(a) - return net.IP6_Address(transmute([8]u16be)ipv4) - - case 0: - return nil - } - panic("Invalid test case") -} - Kind :: enum { IP4, // Decimal IPv4 IP4_Alt, // Non-decimal address @@ -223,10 +103,7 @@ IP_Address_Parsing_Test_Vector :: struct { // Input address to try and parse. input: string, - /* - Hexadecimal representation of the expected numeric value of the address. - Zero length means input is invalid and the parser should report failure. - */ + // Hexadecimal representation of the expected numeric value of the address. Zero length means input is invalid and the parser should report failure. binstr: string, // Expected `address_to_string` output, if a valid input and this string is non-empty. @@ -335,38 +212,30 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{ { .IP6, "c0a8", "", ""}, } -tcp_tests :: proc(t: ^testing.T) { - fmt.println("Testing two servers trying to bind to the same endpoint...") - two_servers_binding_same_endpoint(t) - fmt.println("Testing client connecting to a closed port...") - client_connects_to_closed_port(t) - fmt.println("Testing client sending server data...") - client_sends_server_data(t) -} - -ENDPOINT := net.Endpoint{ - net.IP4_Address{127, 0, 0, 1}, - 9999, -} +ENDPOINT_TWO_SERVERS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9991} +ENDPOINT_CLOSED_PORT := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9992} +ENDPOINT_SERVER_SENDS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9993} @(test) two_servers_binding_same_endpoint :: proc(t: ^testing.T) { - skt1, err1 := net.listen_tcp(ENDPOINT) + skt1, err1 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt1) - skt2, err2 := net.listen_tcp(ENDPOINT) + skt2, err2 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt2) - expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") - expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") + testing.expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") + testing.expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") } @(test) client_connects_to_closed_port :: proc(t: ^testing.T) { - skt, err := net.dial_tcp(ENDPOINT) + + skt, err := net.dial_tcp(ENDPOINT_CLOSED_PORT) defer net.close(skt) - expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") + testing.expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") } + @(test) client_sends_server_data :: proc(t: ^testing.T) { CONTENT: string: "Hellope!" @@ -390,8 +259,8 @@ client_sends_server_data :: proc(t: ^testing.T) { defer sync.wait_group_done(r.wg) - if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { - log(r.t, r.err) + if r.skt, r.err = net.dial_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { + testing.expectf(r.t, false, "[tcp_client:dial_tcp] %v", r.err) return } @@ -405,19 +274,17 @@ client_sends_server_data :: proc(t: ^testing.T) { defer sync.wait_group_done(r.wg) - log(r.t, "tcp_server listen") - if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil { + if r.skt, r.err = net.listen_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { sync.wait_group_done(r.wg) - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:listen_tcp] %v", r.err) return } sync.wait_group_done(r.wg) - log(r.t, "tcp_server accept") client: net.TCP_Socket if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil { - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:accept_tcp] %v", r.err) return } defer net.close(client) @@ -437,10 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - log(t, "waiting for server to start listening") sync.wait_group_wait(&wg) - log(t, "starting up client") - sync.wait_group_add(&wg, 2) thread_data[1].t = t @@ -454,20 +318,15 @@ client_sends_server_data :: proc(t: ^testing.T) { net.close(thread_data[1].skt) thread.destroy(thread_data[1].tid) } - - log(t, "waiting for threads to finish") sync.wait_group_wait(&wg) - log(t, "threads finished") okay := thread_data[0].err == nil && thread_data[1].err == nil - msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) received := string(thread_data[0].data[:thread_data[0].length]) okay = received == CONTENT - msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client to send \"{}\", got \"{}\"", CONTENT, received) } URL_Test :: struct { @@ -559,22 +418,15 @@ split_url_test :: proc(t: ^testing.T) { delete(test.queries) } - msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme) - expect(t, scheme == test.scheme, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host) - expect(t, host == test.host, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path) - expect(t, path == test.path, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) - expect(t, len(queries) == len(test.queries), msg) + testing.expectf(t, scheme == test.scheme, "Expected `net.split_url` to return %s, got %s", test.scheme, scheme) + testing.expectf(t, host == test.host, "Expected `net.split_url` to return %s, got %s", test.host, host) + testing.expectf(t, path == test.path, "Expected `net.split_url` to return %s, got %s", test.path, path) + testing.expectf(t, len(queries) == len(test.queries), "Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) for k, v in queries { expected := test.queries[k] - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v) - expect(t, v == expected, msg) + testing.expectf(t, v == expected, "Expected `net.split_url` to return %s, got %s", expected, v) } - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment) - expect(t, fragment == test.fragment, msg) - + testing.expectf(t, fragment == test.fragment, "Expected `net.split_url` to return %s, got %s", test.fragment, fragment) } } @@ -659,7 +511,45 @@ join_url_test :: proc(t: ^testing.T) { for test_url in test.url { pass |= url == test_url } - msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url) - expect(t, pass, msg) + testing.expectf(t, pass, "Expected `net.join_url` to return one of %s, got %s", test.url, url) } } + +@(private) +address_to_binstr :: proc(address: net.Address) -> (binstr: string) { + switch t in address { + case net.IP4_Address: + b := transmute(u32be)t + return fmt.tprintf("%08x", b) + case net.IP6_Address: + b := transmute(u128be)t + return fmt.tprintf("%32x", b) + case: + return "" + } + unreachable() +} + +@(private) +binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Address) { + switch len(binstr) { + case 8: // IPv4 + a, ok := strconv.parse_u64_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u32be(a) + return net.IP4_Address(transmute([4]u8)ipv4) + + + case 32: // IPv6 + a, ok := strconv.parse_u128_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u128be(a) + return net.IP6_Address(transmute([8]u16be)ipv4) + + case 0: + return nil + } + panic("Invalid test case") +} \ No newline at end of file From 8d93379e299b8115c2727ba1a442609ac4293416 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:10:55 +0200 Subject: [PATCH 069/151] Factor benchmarks out into tests\benchmark\ --- .github/workflows/ci.yml | 23 ++ tests/benchmark/Makefile | 14 + tests/benchmark/crypto/benchmark_crypto.odin | 356 +++++++++++++++++++ tests/benchmark/hash/benchmark_hash.odin | 218 ++++++++++++ tests/core/crypto/test_crypto_benchmark.odin | 353 ------------------ tests/core/hash/test_core_hash.odin | 232 +----------- 6 files changed, 615 insertions(+), 581 deletions(-) create mode 100644 tests/benchmark/Makefile create mode 100644 tests/benchmark/crypto/benchmark_crypto.odin create mode 100644 tests/benchmark/hash/benchmark_hash.odin delete mode 100644 tests/core/crypto/test_crypto_benchmark.odin diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb2077d1..67b2bfbda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: (cd tests/core; gmake all_bsd) (cd tests/internal; gmake all_bsd) (cd tests/issues; ./run.sh) + (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest @@ -80,6 +81,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 timeout-minutes: 10 @@ -131,6 +137,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 build_macOS_arm: name: MacOS ARM Build, Check, and Test runs-on: macos-14 # This is an arm/m1 runner. @@ -170,6 +181,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 build_windows: name: Windows Build, Check, and Test runs-on: windows-2022 @@ -245,6 +261,13 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 + - name: core library benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + cd tests\benchmark + call build.bat + timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | diff --git a/tests/benchmark/Makefile b/tests/benchmark/Makefile new file mode 100644 index 000000000..8a8e08b24 --- /dev/null +++ b/tests/benchmark/Makefile @@ -0,0 +1,14 @@ +ODIN=../../odin +COMMON=-no-bounds-check -vet -strict-style + +all: crypto_bench \ + hash_bench + +crypto_bench: + $(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto + +hash_bench: + $(ODIN) test hash $(COMMON) -o:speed -out:bench_hash + +clean: + rm bench_* \ No newline at end of file diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin new file mode 100644 index 000000000..e90216ad6 --- /dev/null +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -0,0 +1,356 @@ +package benchmark_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:fmt" +import "core:log" +import "core:strings" +import "core:testing" +import "core:time" + +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:crypto/ed25519" +import "core:crypto/poly1305" +import "core:crypto/x25519" + +// Cryptographic primitive benchmarks. + +@(test) +benchmark_crypto :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + log.info(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "ChaCha20 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "Poly1305 64 zero bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "Poly1305 1024 zero bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "chacha20poly1305 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "AES256-GCM 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_gcm, + teardown = _teardown_sized_buf, + } + + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key[:]) + + context.user_ptr = &ctx + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + iters :: 10000 + + priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) + priv_key: ed25519.Private_Key + start := time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) + assert(ok, "private key should deserialize") + } + elapsed := time.since(start) + fmt.sbprintfln(&str, + "ed25519.private_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" + pub_key: ed25519.Public_Key + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) + assert(ok, "public key should deserialize") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.public_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + msg := "Got a job for you, 621." + sig_bytes: [ed25519.SIGNATURE_SIZE]byte + msg_bytes := transmute([]byte)(msg) + start = time.now() + for i := 0; i < iters; i = i + 1 { + ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.sign: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) + assert(ok, "signature should validate") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.verify: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } + { + point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + + point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) + scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) + out: [x25519.POINT_SIZE]byte = --- + + iters :: 10000 + start := time.now() + for i := 0; i < iters; i = i + 1 { + x25519.scalarmult(out[:], scalar[:], point[:]) + } + elapsed := time.since(start) + + fmt.sbprintfln(&str, + "x25519.scalarmult: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } +} + +@(private) +_setup_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +@(private) +_teardown_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + delete(options.input) + return nil +} + +@(private) +_benchmark_chacha20 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + ctx: chacha20.Context = --- + chacha20.init(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + chacha20.xor_bytes(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +_benchmark_poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [poly1305.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + + tag: [poly1305.TAG_SIZE]byte = --- + for _ in 0 ..= options.rounds { + poly1305.sum(tag[:], buf, key[:]) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + //options.hash = u128(h) + return nil +} + +@(private) +_benchmark_chacha20poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + tag: [chacha20poly1305.TAG_SIZE]byte = --- + + for _ in 0 ..= options.rounds { + chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +_benchmark_aes256_gcm :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + nonce: [aes.GCM_NONCE_SIZE]byte + tag: [aes.GCM_TAG_SIZE]byte = --- + + ctx := transmute(^aes.Context_GCM)context.user_ptr + + for _ in 0 ..= options.rounds { + aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} diff --git a/tests/benchmark/hash/benchmark_hash.odin b/tests/benchmark/hash/benchmark_hash.odin new file mode 100644 index 000000000..84eb827e7 --- /dev/null +++ b/tests/benchmark/hash/benchmark_hash.odin @@ -0,0 +1,218 @@ +package benchmark_core_hash + +import "core:fmt" +import "core:hash/xxhash" +import "base:intrinsics" +import "core:strings" +import "core:testing" +import "core:time" + +@(test) +benchmark_hash :: proc(t: ^testing.T) { + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + fmt.println(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "XXH32 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x85f6413c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH32 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x9430f97f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x17bb1103c92c502f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x87d2a1b6e1163ef1) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0xb6ef17a3448492b6918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } +} + +// Benchmarks + +setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + delete(options.input) + return nil +} + +benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u32 + for _ in 0..=options.rounds { + h = xxhash.XXH32(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH3_64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u128 + for _ in 0..=options.rounds { + h = xxhash.XXH3_128(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = h + return nil +} + +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} \ No newline at end of file diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin deleted file mode 100644 index 5bfc579bd..000000000 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ /dev/null @@ -1,353 +0,0 @@ -package test_core_crypto - -import "base:runtime" -import "core:encoding/hex" -import "core:log" -import "core:testing" -import "core:time" - -import "core:crypto/aes" -import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" -import "core:crypto/ed25519" -import "core:crypto/poly1305" -import "core:crypto/x25519" - -// Cryptographic primitive benchmarks. - -@(test) -bench_crypto :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - bench_chacha20(t) - bench_poly1305(t) - bench_chacha20poly1305(t) - bench_aes256_gcm(t) - bench_ed25519(t) - bench_x25519(t) -} - -_setup_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -_teardown_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - delete(options.input) - return nil -} - -_benchmark_chacha20 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) - - for _ in 0 ..= options.rounds { - chacha20.xor_bytes(&ctx, buf, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [poly1305.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - - tag: [poly1305.TAG_SIZE]byte = --- - for _ in 0 ..= options.rounds { - poly1305.sum(tag[:], buf, key[:]) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - //options.hash = u128(h) - return nil -} - -_benchmark_chacha20poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - tag: [chacha20poly1305.TAG_SIZE]byte = --- - - for _ in 0 ..= options.rounds { - chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_aes256_gcm :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - nonce: [aes.GCM_NONCE_SIZE]byte - tag: [aes.GCM_TAG_SIZE]byte = --- - - ctx := transmute(^aes.Context_GCM)context.user_ptr - - for _ in 0 ..= options.rounds { - aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - log.infof( - "\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - ) -} - -bench_chacha20 :: proc(t: ^testing.T) { - name := "ChaCha20 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_poly1305 :: proc(t: ^testing.T) { - name := "Poly1305 64 zero bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "Poly1305 1024 zero bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_chacha20poly1305 :: proc(t: ^testing.T) { - name := "chacha20poly1305 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_aes256_gcm :: proc(t: ^testing.T) { - name := "AES256-GCM 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_aes256_gcm, - teardown = _teardown_sized_buf, - } - - key := [aes.KEY_SIZE_256]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - ctx: aes.Context_GCM - aes.init_gcm(&ctx, key[:]) - - context.user_ptr = &ctx - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "AES256-GCM 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "AES256-GCM 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_ed25519 :: proc(t: ^testing.T) { - iters :: 10000 - - priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) - priv_key: ed25519.Private_Key - start := time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - assert(ok, "private key should deserialize") - } - elapsed := time.since(start) - log.infof( - "ed25519.private_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" - pub_key: ed25519.Public_Key - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) - assert(ok, "public key should deserialize") - } - elapsed = time.since(start) - log.infof( - "ed25519.public_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - msg := "Got a job for you, 621." - sig_bytes: [ed25519.SIGNATURE_SIZE]byte - msg_bytes := transmute([]byte)(msg) - start = time.now() - for i := 0; i < iters; i = i + 1 { - ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) - } - elapsed = time.since(start) - log.infof( - "ed25519.sign: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) - assert(ok, "signature should validate") - } - elapsed = time.since(start) - log.infof( - "ed25519.verify: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) -} - -bench_x25519 :: proc(t: ^testing.T) { - point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - - point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) - scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) - out: [x25519.POINT_SIZE]byte = --- - - iters :: 10000 - start := time.now() - for i := 0; i < iters; i = i + 1 { - x25519.scalarmult(out[:], scalar[:], point[:]) - } - elapsed := time.since(start) - - log.infof( - "x25519.scalarmult: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) -} diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index a6294de55..c332383e7 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -2,10 +2,7 @@ package test_core_hash import "core:hash/xxhash" import "core:hash" -import "core:time" import "core:testing" -import "core:fmt" -import "core:log" import "core:math/rand" import "base:intrinsics" @@ -78,15 +75,10 @@ test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) { xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) - - testing.expect(t, xxh32 == v.xxh_32, xxh32_error) - testing.expect(t, xxh64 == v.xxh_64, xxh64_error) - testing.expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - testing.expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) } } @@ -174,220 +166,4 @@ test_crc64_vectors :: proc(t: ^testing.T) { testing.expectf(t, iso == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso) testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2) } -} - -@(test) -test_benchmark_xxh32 :: proc(t: ^testing.T) { - name := "XXH32 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x85f6413c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh32_1MB :: proc(t: ^testing.T) { - name := "XXH32 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x9430f97f) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh64 :: proc(t: ^testing.T) { - name := "XXH64 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x17bb1103c92c502f) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh64_1MB :: proc(t: ^testing.T) { - name := "XXH64 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x87d2a1b6e1163ef1) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_64 :: proc(t: ^testing.T) { - name := "XXH3_64 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh3_64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x801fedc74ccd608c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_64_1MB :: proc(t: ^testing.T) { - name := "XXH3_64 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh3_64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x918780b90550bf34) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_128 :: proc(t: ^testing.T) { - name := "XXH3_128 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh3_128, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_128_1MB :: proc(t: ^testing.T) { - name := "XXH3_128 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh3_128, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0xb6ef17a3448492b6918780b90550bf34) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -// Benchmarks - -setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - delete(options.input) - return nil -} - -benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u32 - for _ in 0..=options.rounds { - h = xxhash.XXH32(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH3_64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u128 - for _ in 0..=options.rounds { - h = xxhash.XXH3_128(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = h - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options, loc := #caller_location) { - log.infof("\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - location=loc, - ) } \ No newline at end of file From 54dae06ad1d727a6607024cf7d118e53e9981289 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:19:17 +0200 Subject: [PATCH 070/151] Update CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67b2bfbda..7180fbd15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,7 +261,7 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 - - name: core library benchmarks + - name: Core library benchmarks shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat From 3354212f8eda24f1807bc04247911db000c0159f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:32:25 +0200 Subject: [PATCH 071/151] Update ci.yml Disable benchmark on Windows for a moment. --- .github/workflows/ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7180fbd15..2ee557846 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,13 +261,6 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 - - name: Core library benchmarks - shell: cmd - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\benchmark - call build.bat - timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | From 306169699c6f59d910fc38e357124de6f93c78dd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Jun 2024 13:07:40 +0200 Subject: [PATCH 072/151] Update ci.yml --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ee557846..c5493775f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,6 +233,13 @@ jobs: cd tests\core call build.bat timeout-minutes: 10 + - name: Core library benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + cd tests\benchmark + call build.bat + timeout-minutes: 10 - name: Vendor library tests shell: cmd run: | From 6050bc3bf66f5b2159f339a1b62f855fd774b966 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Jun 2024 13:17:59 +0200 Subject: [PATCH 073/151] Add missing benchmarks build.bat. --- tests/benchmark/build.bat | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/benchmark/build.bat diff --git a/tests/benchmark/build.bat b/tests/benchmark/build.bat new file mode 100644 index 000000000..e9051855b --- /dev/null +++ b/tests/benchmark/build.bat @@ -0,0 +1,13 @@ +@echo off +set COMMON=-no-bounds-check -vet -strict-style +set PATH_TO_ODIN==..\..\odin + +echo --- +echo Running core:crypto benchmarks +echo --- +%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b + +echo --- +echo Running core:hash benchmarks +echo --- +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b \ No newline at end of file From fb37572c4ce9d8058cd73c0b7c21c1ecd2fdad65 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 17:02:32 -0400 Subject: [PATCH 074/151] Rename `signal_handler.odin` to `signal_handler_libc.odin` --- core/testing/{signal_handler.odin => signal_handler_libc.odin} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/testing/{signal_handler.odin => signal_handler_libc.odin} (100%) diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler_libc.odin similarity index 100% rename from core/testing/signal_handler.odin rename to core/testing/signal_handler_libc.odin From d1723664a79a5818e8b43dc43210270dfdcb7b92 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 17:46:28 -0400 Subject: [PATCH 075/151] Catch `SIGILL`, `SIGFPE`, `SIGSEGV` in the test runner --- core/testing/runner.odin | 65 ++++++++++++- core/testing/signal_handler.odin | 33 +++++++ core/testing/signal_handler_libc.odin | 126 +++++++++++++++++++++++-- core/testing/signal_handler_other.odin | 12 ++- 4 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 core/testing/signal_handler.odin diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 47ae5d528..63b3864dd 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -69,6 +69,8 @@ Task_Timeout :: struct { run_test_task :: proc(task: thread.Task) { data := cast(^Task_Data)(task.data) + setup_task_signal_handler(task.user_index) + chan.send(data.t.channel, Event_New_Test { test_index = task.user_index, }) @@ -76,6 +78,8 @@ run_test_task :: proc(task: thread.Task) { chan.send(data.t.channel, Event_State_Change { new_state = .Running, }) + + context.assertion_failure_proc = test_assertion_failure_proc context.logger = { procedure = test_logger_proc, @@ -389,6 +393,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) when FANCY_OUTPUT { + signals_were_raised := false + redraw_report(stdout, report) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } @@ -557,12 +563,57 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - if should_abort() { + if should_stop_runner() { fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.") thread.pool_shutdown(&pool) break main_loop } + when FANCY_OUTPUT { + // Because the bounds checking procs send directly to STDERR with + // no way to redirect or handle them, we need to at least try to + // let the user see those messages when using the animated progress + // report. This flag may be set by the block of code below if a + // signal is raised. + // + // It'll be purely by luck if the output is interleaved properly, + // given the nature of non-thread-safe printing. + // + // At worst, if Odin did not print any error for this signal, we'll + // just re-display the progress report. The fatal log error message + // should be enough to clue the user in that something dire has + // occurred. + bypass_progress_overwrite := false + } + + if test_index, reason, ok := should_stop_test(); ok { + #no_bounds_check report.all_test_states[test_index] = .Failed + #no_bounds_check it := internal_tests[test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + fmt.assertf(thread.pool_stop_task(&pool, test_index), + "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", + reason, test_index, it.pkg, it.name) + + if test_index not_in failed_test_reason_map { + // We only write a new error message here if there wasn't one + // already, because the message we can provide based only on + // the signal won't be very useful, whereas asserts and panics + // will provide a user-written error message. + failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator) + pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) + + when FANCY_OUTPUT { + signals_were_raised = true + bypass_progress_overwrite = true + } + } + + total_failure_count += 1 + total_done_count += 1 + } + // -- Redraw. when FANCY_OUTPUT { @@ -570,7 +621,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { continue main_loop } - fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + if !bypass_progress_overwrite { + fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + } } else { if total_done_count != last_done_count { fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) @@ -698,6 +751,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + when FANCY_OUTPUT { + if signals_were_raised { + fmt.wprintln(batch_writer, ` +Signals were raised during this test run. Log messages are likely to have collided with each other. +To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_FANCY=false option.`) + } + } + fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) return total_success_count == total_test_count diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin new file mode 100644 index 000000000..891f6bbb6 --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,33 @@ +//+private +package testing + +import "base:runtime" +import pkg_log "core:log" + +Stop_Reason :: enum { + Unknown, + Illegal_Instruction, + Arithmetic_Error, + Segmentation_Fault, +} + +test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { + pkg_log.fatalf("%s: %s", prefix, message, location = loc) + runtime.trap() +} + +setup_signal_handler :: proc() { + _setup_signal_handler() +} + +setup_task_signal_handler :: proc(test_index: int) { + _setup_task_signal_handler(test_index) +} + +should_stop_runner :: proc() -> bool { + return _should_stop_runner() +} + +should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return _should_stop_test() +} diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 2dd0ff192..ff3dbe135 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -4,16 +4,126 @@ package testing import "base:intrinsics" import "core:c/libc" +import "core:encoding/ansi" +import "core:sync" + +@(private="file") stop_runner_flag: libc.sig_atomic_t + +@(private="file") stop_test_gate: sync.Mutex +@(private="file") stop_test_index: libc.sig_atomic_t +@(private="file") stop_test_reason: libc.sig_atomic_t +@(private="file") stop_test_alert: libc.sig_atomic_t + +@(private="file", thread_local) +local_test_index: libc.sig_atomic_t @(private="file") -abort_flag: libc.sig_atomic_t - -setup_signal_handler :: proc() { - libc.signal(libc.SIGINT, proc "c" (sig: libc.int) { - intrinsics.atomic_add(&abort_flag, 1) - }) +stop_runner_callback :: proc "c" (sig: libc.int) { + intrinsics.atomic_store(&stop_runner_flag, 1) } -should_abort :: proc() -> bool { - return intrinsics.atomic_load(&abort_flag) > 0 +@(private="file") +stop_test_callback :: proc "c" (sig: libc.int) { + if local_test_index == -1 { + // We're the test runner, and we ourselves have caught a signal from + // which there is no recovery. + // + // The most we can do now is make sure the user's cursor is visible, + // nuke the entire processs, and hope a useful core dump survives. + + // NOTE(Feoramund): Using these write calls in a signal handler is + // undefined behavior in C99 but possibly tolerated in POSIX 2008. + // Either way, we may as well try to salvage what we can. + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) + libc.fflush(libc.stdout) + + // This is an attempt at being compliant by avoiding printf. + sigbuf: [8]byte + sigstr: string + { + signum := cast(int)sig + i := len(sigbuf) - 2 + for signum > 0 { + m := signum % 10 + signum /= 10 + sigbuf[i] = cast(u8)('0' + m) + i -= 1 + } + sigstr = cast(string)sigbuf[i:] + } + + advisory_a := ` +The test runner's main thread has caught an unrecoverable error (signal ` + advisory_b := `) and will now forcibly terminate. +This is a dire bug and should be reported to the Odin developers. +` + libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr) + libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr) + libc.fwrite(raw_data(advisory_b), size_of(byte), len(advisory_b), libc.stderr) + + // Try to get a core dump. + libc.abort() + } + + if sync.mutex_guard(&stop_test_gate) { + intrinsics.atomic_store(&stop_test_index, local_test_index) + intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig) + intrinsics.atomic_store(&stop_test_alert, 1) + + for { + // Idle until this thread is terminated by the runner, + // otherwise we may continue to generate signals. + intrinsics.cpu_relax() + } + } +} + +_setup_signal_handler :: proc() { + local_test_index = -1 + + // Catch user interrupt / CTRL-C. + libc.signal(libc.SIGINT, stop_runner_callback) + // Catch polite termination request. + libc.signal(libc.SIGTERM, stop_runner_callback) + + // For tests: + // Catch asserts and panics. + libc.signal(libc.SIGILL, stop_test_callback) + // Catch arithmetic errors. + libc.signal(libc.SIGFPE, stop_test_callback) + // Catch segmentation faults (illegal memory access). + libc.signal(libc.SIGSEGV, stop_test_callback) +} + +_setup_task_signal_handler :: proc(test_index: int) { + local_test_index = cast(libc.sig_atomic_t)test_index +} + +_should_stop_runner :: proc() -> bool { + return intrinsics.atomic_load(&stop_runner_flag) == 1 +} + +@(private="file") +unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) { + if ok { + sync.mutex_unlock(&stop_test_gate) + } +} + +@(deferred_out=unlock_stop_test_gate) +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + if intrinsics.atomic_load(&stop_test_alert) == 1 { + intrinsics.atomic_store(&stop_test_alert, 0) + + test_index = cast(int)intrinsics.atomic_load(&stop_test_index) + switch intrinsics.atomic_load(&stop_test_reason) { + case libc.SIGFPE: reason = .Arithmetic_Error + case libc.SIGILL: reason = .Illegal_Instruction + case libc.SIGSEGV: reason = .Segmentation_Fault + } + ok = true + } + + return } diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index b2e2ea906..8621349f0 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -2,10 +2,18 @@ //+build js, wasi, freestanding package testing -setup_signal_handler :: proc() { +_setup_signal_handler :: proc() { // Do nothing. } -should_abort :: proc() -> bool { +_setup_task_signal_handler :: proc(test_index: int) { + // Do nothing. +} + +_should_stop_runner :: proc() -> bool { return false } + +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return 0, {}, false +} From 433ca538bfccdfca9704942c4575b582b36db9ad Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 18:01:22 -0400 Subject: [PATCH 076/151] Be specific about platforms not implementing test runner signal handler --- core/testing/signal_handler_other.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index 8621349f0..04981f5af 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -1,5 +1,5 @@ //+private -//+build js, wasi, freestanding +//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku package testing _setup_signal_handler :: proc() { From 6a5d51f0d6c82d350e9082c1132fd1f48720d4c0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 31 May 2024 15:45:06 -0400 Subject: [PATCH 077/151] Use more concise way of satisfying `-vet` --- core/testing/runner.odin | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 63b3864dd..64c1ad760 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -5,23 +5,18 @@ import "base:intrinsics" import "base:runtime" import "core:bytes" import "core:encoding/ansi" -import "core:encoding/base64" +@require import "core:encoding/base64" import "core:fmt" import "core:io" -import pkg_log "core:log" +@require import pkg_log "core:log" import "core:mem" import "core:os" import "core:slice" -import "core:strings" +@require import "core:strings" import "core:sync/chan" import "core:thread" import "core:time" -// Keep `-vet` happy. -base64_encode :: base64.encode -_ :: pkg_log -_ :: strings - // Specify how many threads to use when running tests. TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. @@ -728,7 +723,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) } - encoded_names := base64_encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) + encoded_names := base64.encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) fmt.wprintf(batch_writer, ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + From cb8faf5b74cd0863e226908d2bebd4829b71cbc8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:04:20 -0400 Subject: [PATCH 078/151] Remove `-test-name` in favor of test runner option `-define:ODIN_TEST_NAMES=...` is capable of selecting test by package and name or name only, with the ability to access packages included by `-all-packages`. --- src/build_settings.cpp | 1 - src/checker.cpp | 29 ----------------------------- src/main.cpp | 24 ------------------------ 3 files changed, 54 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 376e56a8e..7bdec376b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -893,7 +893,6 @@ struct BuildContext { u32 cmd_doc_flags; Array extra_packages; - StringSet test_names; bool test_all_packages; gbAffinity affinity; diff --git a/src/checker.cpp b/src/checker.cpp index 2fd274975..97e685d33 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5852,35 +5852,6 @@ gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Arrayinfo.testing_procedures, init_procedures_cmp); remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures); - - if (build_context.test_names.entries.count == 0) { - return; - } - - AstPackage *pkg = c->info.init_package; - Scope *s = pkg->scope; - - for (String const &name : build_context.test_names) { - Entity *e = scope_lookup(s, name); - if (e == nullptr) { - Token tok = {}; - if (pkg->files.count != 0) { - tok = pkg->files[0]->tokens[0]; - } - error(tok, "Unable to find the test '%.*s' in 'package %.*s' ", LIT(name), LIT(pkg->name)); - } - } - - for (isize i = 0; i < c->info.testing_procedures.count; /**/) { - Entity *e = c->info.testing_procedures[i]; - String name = e->token.string; - if (!string_set_exists(&build_context.test_names, name)) { - array_ordered_remove(&c->info.testing_procedures, i); - } else { - i += 1; - } - } - } diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..e7f076c7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -276,8 +276,6 @@ enum BuildFlagKind { BuildFlag_RelocMode, BuildFlag_DisableRedZone, - BuildFlag_TestName, - BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, BuildFlag_DefaultToPanicAllocator, @@ -471,8 +469,6 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_RelocMode, str_lit("reloc-mode"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_DisableRedZone, str_lit("disable-red-zone"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test); - add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); @@ -1119,21 +1115,6 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_DisableRedZone: build_context.disable_red_zone = true; break; - case BuildFlag_TestName: { - GB_ASSERT(value.kind == ExactValue_String); - { - String name = value.value_string; - if (!string_is_valid_identifier(name)) { - gb_printf_err("Test name '%.*s' must be a valid identifier\n", LIT(name)); - bad_flags = true; - break; - } - string_set_add(&build_context.test_names, name); - - // NOTE(bill): Allow for multiple -test-name - continue; - } - } case BuildFlag_DisallowDo: build_context.disallow_do = true; break; @@ -1962,10 +1943,6 @@ gb_internal void print_show_help(String const arg0, String const &command) { } if (test_only) { - print_usage_line(1, "-test-name:"); - print_usage_line(2, "Runs specific test only by name."); - print_usage_line(0, ""); - print_usage_line(1, "-all-packages"); print_usage_line(2, "Tests all packages imported into the given initial package."); print_usage_line(0, ""); @@ -2489,7 +2466,6 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init args"); map_init(&build_context.defined_values); build_context.extra_packages.allocator = heap_allocator(); - string_set_init(&build_context.test_names); Array args = setup_args(arg_count, arg_ptr); From 45fa9d81487e89b88e4b5cd22803be45ea341f85 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:12:51 -0400 Subject: [PATCH 079/151] Expand documentation comment for `ODIN_TEST_NAMES` --- core/testing/runner.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 64c1ad760..9027750da 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -26,6 +26,9 @@ ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false) // Specify how much memory each thread allocator starts with. PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. +// Each test is separated by a comma and may optionally include the package name. +// This may be useful when running tests on multiple packages with `-all-packages`. +// The format is: `package.test_name,test_name_only,...` TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) From 21064fbb60ac9b3a838b100e4718e1e66dfdf80f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:15:27 -0400 Subject: [PATCH 080/151] Clear thread pool task data on restart --- core/thread/thread_pool.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 53f89ea0c..6d80cb619 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -180,6 +180,7 @@ pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool replacement := create(pool_thread_runner) replacement.user_index = t.user_index replacement.data = data + data.task = {} pool.threads[i] = replacement start(replacement) @@ -211,6 +212,7 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { replacement := create(pool_thread_runner) replacement.user_index = t.user_index replacement.data = data + data.task = {} pool.threads[i] = replacement start(replacement) From 9dcf3457952c32e34baa9136c5c4163cb4a55ceb Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:16:14 -0400 Subject: [PATCH 081/151] Set thread pool `is_running` to false on shutdown --- core/thread/thread_pool.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 6d80cb619..da5e116ff 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -225,6 +225,7 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { // // The pool must still be destroyed after this. pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) { + intrinsics.atomic_store(&pool.is_running, false) sync.guard(&pool.mutex) for t in pool.threads { From ccdbd4b6cecfb5712edac97d9b22cc37c141a542 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:30:02 -0400 Subject: [PATCH 082/151] Simplify casts in `mem.Rollback_Stack` procs --- core/mem/rollback_stack_allocator.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index d159897cd..f5e428d87 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -63,9 +63,9 @@ Rollback_Stack :: struct { @(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { - start := cast(uintptr)raw_data(block.buffer) - end := cast(uintptr)raw_data(block.buffer) + block.offset - return start < cast(uintptr)ptr && cast(uintptr)ptr <= end + start := raw_data(block.buffer) + end := start[block.offset:] + return start < ptr && ptr <= end } @(private="file", require_results) @@ -105,8 +105,8 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ header := header for block.offset > 0 && header.is_free { block.offset = header.prev_offset - block.last_alloc = cast(rawptr)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr) - header = cast(^Rollback_Stack_Header)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr - size_of(Rollback_Stack_Header)) + block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] + header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[header.prev_ptr - size_of(Rollback_Stack_Header):]) } } @@ -189,8 +189,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt } } - start := cast(uintptr)raw_data(block.buffer) + block.offset - padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + start := raw_data(block.buffer)[block.offset:] + padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { when !ODIN_DISABLE_ASSERT { @@ -202,8 +202,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt continue } - header := cast(^Rollback_Stack_Header)(start + cast(uintptr)padding - size_of(Rollback_Stack_Header)) - ptr := (cast([^]byte)(start + cast(uintptr)padding)) + header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):]) + ptr := start[padding:] header^ = { prev_offset = block.offset, From 4875f745c832119e634197a7eb6403a30f82a95b Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:42:23 -0400 Subject: [PATCH 083/151] Remove Windows test runner in favor of `libc` implementation --- core/testing/runner.odin | 2 +- core/testing/runner_other.odin | 8 - core/testing/runner_windows.odin | 242 ------------------------------- 3 files changed, 1 insertion(+), 251 deletions(-) delete mode 100644 core/testing/runner_other.odin delete mode 100644 core/testing/runner_windows.odin diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 9027750da..8b156eb22 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -88,7 +88,7 @@ run_test_task :: proc(task: thread.Task) { free_all(context.temp_allocator) - run_internal_test(&data.t, data.it) + data.it.p(&data.t) end_t(&data.t) diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin deleted file mode 100644 index 29f338828..000000000 --- a/core/testing/runner_other.odin +++ /dev/null @@ -1,8 +0,0 @@ -//+private -//+build !windows -package testing - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - // TODO(bill): Catch panics on other platforms - it.p(t) -} diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index 23dab4f31..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,242 +0,0 @@ -//+private -//+build windows -package testing - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - it.p(t) -} - -// Temporarily disabled during multi-threaded test runner refactor. -/* -import win32 "core:sys/windows" -import "base:runtime" -import "base:intrinsics" -import "core:time" - -Sema :: struct { - count: i32, -} - -sema_reset :: proc "contextless" (s: ^Sema) { - intrinsics.atomic_store(&s.count, 0) -} -sema_wait :: proc "contextless" (s: ^Sema) { - for { - original_count := s.count - for original_count == 0 { - win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), win32.INFINITE) - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return - } - } -} -sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { - if duration <= 0 { - return false - } - for { - - original_count := intrinsics.atomic_load(&s.count) - for start := time.tick_now(); original_count == 0; /**/ { - if intrinsics.atomic_load(&s.count) != original_count { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false - } - ms := u32(remaining/time.Millisecond) - if !win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), ms) { - return false - } - } - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return true - } - } -} - -sema_post :: proc "contextless" (s: ^Sema, count := 1) { - intrinsics.atomic_add(&s.count, i32(count)) - if count == 1 { - win32.WakeByAddressSingle(&s.count) - } else { - win32.WakeByAddressAll(&s.count) - } -} - - - -Thread_Proc :: #type proc(^Thread) - -MAX_USER_ARGUMENTS :: 8 - -Thread :: struct { - using specific: Thread_Os_Specific, - procedure: Thread_Proc, - - t: ^T, - it: Internal_Test, - success: bool, - - init_context: Maybe(runtime.Context), - - creation_allocator: runtime.Allocator, - - internal_fail_timeout: time.Duration, - internal_fail_timeout_loc: runtime.Source_Code_Location, -} - -Thread_Os_Specific :: struct { - win32_thread: win32.HANDLE, - win32_thread_id: win32.DWORD, - done: bool, // see note in `is_done` -} - -thread_create :: proc(procedure: Thread_Proc) -> ^Thread { - __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { - t := (^Thread)(t_) - context = t.init_context.? or_else runtime.default_context() - - t.procedure(t) - - if t.init_context == nil { - if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { - runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) - } - } - - intrinsics.atomic_store(&t.done, true) - return 0 - } - - - thread := new(Thread) - if thread == nil { - return nil - } - thread.creation_allocator = context.allocator - - win32_thread_id: win32.DWORD - win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id) - if win32_thread == nil { - free(thread, thread.creation_allocator) - return nil - } - thread.procedure = procedure - thread.win32_thread = win32_thread - thread.win32_thread_id = win32_thread_id - thread.init_context = context - - return thread -} - -thread_start :: proc "contextless" (thread: ^Thread) { - win32.ResumeThread(thread.win32_thread) -} - -thread_join_and_destroy :: proc(thread: ^Thread) { - if thread.win32_thread != win32.INVALID_HANDLE { - win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE) - win32.CloseHandle(thread.win32_thread) - thread.win32_thread = win32.INVALID_HANDLE - } - free(thread, thread.creation_allocator) -} - -thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { - win32.TerminateThread(thread.win32_thread, u32(exit_code)) -} - - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc) - - thread := thread_create(proc(thread: ^Thread) { - t := thread.t - timeout := thread.internal_fail_timeout - if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) { - fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc) - } - }) - thread.internal_fail_timeout = duration - thread.internal_fail_timeout_loc = loc - thread.t = t - global_fail_timeout_thread = thread - thread_start(thread) -} - -global_fail_timeout_thread: ^Thread -global_fail_timeout_semaphore: Sema - -global_threaded_runner_semaphore: Sema -global_exception_handler: rawptr -global_current_thread: ^Thread -global_current_t: ^T - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - thread := thread_create(proc(thread: ^Thread) { - exception_handler_proc :: proc "system" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG { - switch ExceptionInfo.ExceptionRecord.ExceptionCode { - case - win32.EXCEPTION_DATATYPE_MISALIGNMENT, - win32.EXCEPTION_BREAKPOINT, - win32.EXCEPTION_ACCESS_VIOLATION, - win32.EXCEPTION_ILLEGAL_INSTRUCTION, - win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, - win32.EXCEPTION_STACK_OVERFLOW: - - sema_post(&global_threaded_runner_semaphore) - return win32.EXCEPTION_EXECUTE_HANDLER - } - - return win32.EXCEPTION_CONTINUE_SEARCH - } - global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc) - - context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - errorf(global_current_t, "%s %s", prefix, message, loc=loc) - intrinsics.trap() - } - - t := thread.t - - global_fail_timeout_thread = nil - sema_reset(&global_fail_timeout_semaphore) - - thread.it.p(t) - - sema_post(&global_fail_timeout_semaphore) - if global_fail_timeout_thread != nil do thread_join_and_destroy(global_fail_timeout_thread) - - thread.success = true - sema_post(&global_threaded_runner_semaphore) - }) - - sema_reset(&global_threaded_runner_semaphore) - global_current_t = t - - t._fail_now = proc() -> ! { - intrinsics.trap() - } - - thread.t = t - thread.it = it - thread.success = false - thread_start(thread) - - sema_wait(&global_threaded_runner_semaphore) - thread_terminate(thread, int(!thread.success)) - thread_join_and_destroy(thread) - - win32.RemoveVectoredExceptionHandler(global_exception_handler) - - if !thread.success && t.error_count == 0 { - t.error_count += 1 - } - - return -} -*/ From cb00b8022b5041b93a4b066e4b27c2a0368a377c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:56:28 -0400 Subject: [PATCH 084/151] Add note about `SIGSEGV` edge case on UNIX-likes --- core/testing/runner.odin | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 8b156eb22..24b902005 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -659,6 +659,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // -- All tests are complete, or the runner has been interrupted. + // NOTE(Feoramund): If you've arrived here after receiving signal 11 or + // SIGSEGV on the main runner thread, while using a UNIX-like platform, + // there is the possibility that you may have encountered a rare edge case + // involving the joining of threads. + // + // At the time of writing, the thread library is undergoing a rewrite that + // should solve this problem; it is not an issue with the test runner itself. thread.pool_join(&pool) finished_in := time.since(start_time) From 5db65aa796ef5dc2bfecf3a74d718e43ef5b40d0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 08:16:40 -0400 Subject: [PATCH 085/151] Make it easier to learn about `ODIN_TEST_CLIPBOARD` --- core/testing/runner.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 24b902005..ddc01c919 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -748,6 +748,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { #no_bounds_check it := internal_tests[test_index] fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name) } + fmt.wprint(batch_writer, "\n\nIf your terminal supports OSC 52, you may use -define:ODIN_TEST_CLIPBOARD to have this copied directly to your clipboard.") } fmt.wprintln(batch_writer) From 6a9203328bfd34c59abeb46d61f1cb5d238b24a7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:03:36 -0400 Subject: [PATCH 086/151] Log thread count at test run start Provides a helpful info message about the option to change how many threads are used per run. --- core/testing/runner.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ddc01c919..4a45dabd1 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -397,6 +397,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } + when TEST_THREADS == 0 { + pkg_log.infof("Starting test runner with %i thread%s. Set with -define:ODIN_TEST_THREADS=n.", + thread_count, + "" if thread_count == 1 else "s") + } else { + pkg_log.infof("Starting test runner with %i thread%s.", + thread_count, + "" if thread_count == 1 else "s") + } + pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) when TRACKING_MEMORY { From 5e3e958574a7654a70382fb04cea2212f8652ff0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:04:49 -0400 Subject: [PATCH 087/151] Add `-define:ODIN_TEST_LOG_LEVEL` to set lowest log level --- core/testing/runner.odin | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 4a45dabd1..dcc7c10ce 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -39,8 +39,23 @@ PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) // This is the random seed that will be sent to each test. // If it is unspecified, it will be set to the system cycle counter at startup. SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) +// Set the lowest log level for this test run. +LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") +get_log_level :: #force_inline proc() -> runtime.Logger_Level { + when ODIN_DEBUG { + // Always use .Debug in `-debug` mode. + return .Debug + } else { + when LOG_LEVEL == "debug" { return .Debug } + else when LOG_LEVEL == "info" { return .Info } + else when LOG_LEVEL == "warning" { return .Warning } + else when LOG_LEVEL == "error" { return .Error } + else when LOG_LEVEL == "fatal" { return .Fatal } + } +} + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { #no_bounds_check c := t.cleanups[i] @@ -82,7 +97,7 @@ run_test_task :: proc(task: thread.Task) { context.logger = { procedure = test_logger_proc, data = &data.t, - lowest_level = .Debug if ODIN_DEBUG else .Info, + lowest_level = get_log_level(), options = Default_Test_Logger_Opts, } @@ -355,7 +370,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { context.logger = { procedure = runner_logger_proc, data = &log_messages, - lowest_level = .Debug if ODIN_DEBUG else .Info, + lowest_level = get_log_level(), options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, } From d581dbbec52a7ddcb8dbc986240a5d5092659450 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:28:02 -0400 Subject: [PATCH 088/151] Keep test runner main thread from using 100% of a CPU core --- core/testing/runner.odin | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index dcc7c10ce..fbe413bb2 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -439,6 +439,23 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { thread.pool_start(&pool) main_loop: for !thread.pool_is_empty(&pool) { + { + events_pending := thread.pool_num_done(&pool) > 0 + + if !events_pending { + poll_tasks: for &task_channel in task_channels { + if chan.len(task_channel.channel) > 0 { + events_pending = true + break poll_tasks + } + } + } + + if !events_pending { + // Keep the main thread from pegging a core at 100% usage. + time.sleep(1 * time.Microsecond) + } + } cycle_pool: for task in thread.pool_pop_done(&pool) { data := cast(^Task_Data)(task.data) From 890fe07c6e7e1e52407e458ea125d4d29b171c60 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 19:59:20 -0400 Subject: [PATCH 089/151] Disable `FANCY_OUTPUT` in Odin test scripts This should tidy up the CI output logs a bit. --- tests/benchmark/Makefile | 2 +- tests/benchmark/build.bat | 4 ++-- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/math/big/build.bat | 2 +- tests/issues/run.bat | 2 +- tests/issues/run.sh | 2 +- tests/vendor/build.bat | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/benchmark/Makefile b/tests/benchmark/Makefile index 8a8e08b24..840174dbb 100644 --- a/tests/benchmark/Makefile +++ b/tests/benchmark/Makefile @@ -1,5 +1,5 @@ ODIN=../../odin -COMMON=-no-bounds-check -vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false all: crypto_bench \ hash_bench diff --git a/tests/benchmark/build.bat b/tests/benchmark/build.bat index e9051855b..30b030066 100644 --- a/tests/benchmark/build.bat +++ b/tests/benchmark/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin echo --- @@ -10,4 +10,4 @@ echo --- echo --- echo Running core:hash benchmarks echo --- -%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b diff --git a/tests/core/Makefile b/tests/core/Makefile index 9b12f5d76..85f3783b4 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false all: all_bsd \ net_test diff --git a/tests/core/build.bat b/tests/core/build.bat index 3e67b6e13..67ac10f86 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index ad199d775..54b715a4f 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -5,7 +5,7 @@ set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= set OUT_NAME=math_big_test_library.dll -set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style +set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style -define:ODIN_TEST_FANCY=false echo --- echo Running core:math/big tests echo --- diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 41c52c02f..cd23faefb 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -3,7 +3,7 @@ if not exist "build\" mkdir build pushd build -set COMMON=-collection:tests=..\.. +set COMMON=-collection:tests=..\.. -define:ODIN_TEST_FANCY=false @echo on diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 6d53388a7..a7eee8514 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -4,7 +4,7 @@ set -eu mkdir -p build pushd build ODIN=../../../odin -COMMON="-collection:tests=../.." +COMMON="-collection:tests=../.. -define:ODIN_TEST_FANCY=false" NO_NIL_ERR="Error: " diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 693d344f4..1ba53615a 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style +set COMMON=-show-timings -no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin echo --- From 21a1ddfbae92fb78bf536f4047016d0001b76cc2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:03:22 +0200 Subject: [PATCH 090/151] Disable NetBSD tests until 'undefined reference to stdout' is solved. --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5493775f..36914b41b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,6 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 - (cd tests/core; gmake all_bsd) - (cd tests/internal; gmake all_bsd) - (cd tests/issues; ./run.sh) - (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest From 9d8d864400f49e84076fbea843ebf0435d61721a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:12:24 +0200 Subject: [PATCH 091/151] Plug leak in AES tests. --- tests/core/crypto/test_core_crypto_aes.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index 8b96b0ae9..4d4c06bdc 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -15,6 +15,7 @@ test_aes :: proc(t: ^testing.T) { log.info("Testing AES") impls := make([dynamic]aes.Implementation, 0, 2) + defer delete(impls) append(&impls, aes.Implementation.Portable) if aes.is_hardware_accelerated() { append(&impls, aes.Implementation.Hardware) From 60d0c03134dbb65692a0e6208068cf1fb64960d8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:15:25 +0200 Subject: [PATCH 092/151] Strip old test runner back out of `internal`, `issues` and `vendor` --- tests/internal/Makefile | 17 +- tests/internal/build.bat | 15 +- tests/internal/test_128.odin | 50 +--- tests/internal/test_asan.odin | 45 +--- tests/internal/test_map.odin | 162 ++++--------- tests/internal/test_pow.odin | 48 +--- tests/internal/test_rtti.odin | 69 +----- tests/internal/test_string_compare.odin | 60 +---- tests/issues/run.bat | 20 +- tests/issues/run.sh | 22 +- tests/issues/test_issue_1592.odin | 293 ++++++++++++------------ tests/issues/test_issue_2056.odin | 5 +- tests/issues/test_issue_2466.odin | 5 +- tests/issues/test_issue_829.odin | 7 +- tests/vendor/build.bat | 2 +- tests/vendor/glfw/test_vendor_glfw.odin | 44 +--- 16 files changed, 282 insertions(+), 582 deletions(-) diff --git a/tests/internal/Makefile b/tests/internal/Makefile index 09182cd23..fb22767d2 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,23 +1,22 @@ ODIN=../../odin +COMMON=-file -vet -strict-style -o:minimal -all: all_bsd asan_test - -all_bsd: rtti_test map_test pow_test 128_test string_compare_test +all: asan_test rtti_test map_test pow_test 128_test string_compare_test rtti_test: - $(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_rtti.odin $(COMMON) map_test: - $(ODIN) run test_map.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_map.odin $(COMMON) pow_test: - $(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_pow.odin $(COMMON) 128_test: - $(ODIN) run test_128.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_128.odin $(COMMON) asan_test: - $(ODIN) run test_asan.odin -file -sanitize:address -debug + $(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug string_compare_test: - $(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_string_compare.odin $(COMMON) \ No newline at end of file diff --git a/tests/internal/build.bat b/tests/internal/build.bat index 29b3e36c3..edaa2684b 100644 --- a/tests/internal/build.bat +++ b/tests/internal/build.bat @@ -1,10 +1,9 @@ @echo off set PATH_TO_ODIN==..\..\odin -rem %PATH_TO_ODIN% run test_rtti.odin -file -vet -strict-style -o:minimal || exit /b -%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b -rem -define:SEED=42 -%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b - -%PATH_TO_ODIN% run test_128.odin -file -vet -strict-style -o:minimal || exit /b - -%PATH_TO_ODIN% run test_string_compare.odin -file -vet -strict-style -o:minimal || exit /b \ No newline at end of file +set COMMON=-file -vet -strict-style -o:minimal +%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_map.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_pow.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_128.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b \ No newline at end of file diff --git a/tests/internal/test_128.odin b/tests/internal/test_128.odin index 11ef068ed..1aa7487b6 100644 --- a/tests/internal/test_128.odin +++ b/tests/internal/test_128.odin @@ -1,41 +1,7 @@ package test_128 -import "core:fmt" -import "core:os" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_128_align(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_128_align :: proc(t: ^testing.T) { Danger_Struct :: struct { @@ -45,15 +11,15 @@ test_128_align :: proc(t: ^testing.T) { list := [?]Danger_Struct{{0, 0}, {1, 0}, {2, 0}, {3, 0}} - expect(t, list[0].x == 0, fmt.tprintf("[0].x (%v) != 0", list[0].x)) - expect(t, list[0].y == 0, fmt.tprintf("[0].y (%v) != 0", list[0].y)) + testing.expectf(t, list[0].x == 0, "[0].x (%v) != 0", list[0].x) + testing.expectf(t, list[0].y == 0, "[0].y (%v) != 0", list[0].y) - expect(t, list[1].x == 1, fmt.tprintf("[1].x (%v) != 1", list[1].x)) - expect(t, list[1].y == 0, fmt.tprintf("[1].y (%v) != 0", list[1].y)) + testing.expectf(t, list[1].x == 1, "[1].x (%v) != 1", list[1].x) + testing.expectf(t, list[1].y == 0, "[1].y (%v) != 0", list[1].y) - expect(t, list[2].x == 2, fmt.tprintf("[2].x (%v) != 2", list[2].x)) - expect(t, list[2].y == 0, fmt.tprintf("[2].y (%v) != 0", list[2].y)) + testing.expectf(t, list[2].x == 2, "[2].x (%v) != 2", list[2].x) + testing.expectf(t, list[2].y == 0, "[2].y (%v) != 0", list[2].y) - expect(t, list[3].x == 3, fmt.tprintf("[3].x (%v) != 3", list[3].x)) - expect(t, list[3].y == 0, fmt.tprintf("[3].y (%v) != 0", list[3].y)) + testing.expectf(t, list[3].x == 3, "[3].x (%v) != 3", list[3].x) + testing.expectf(t, list[3].y == 0, "[3].y (%v) != 0", list[3].y) } diff --git a/tests/internal/test_asan.odin b/tests/internal/test_asan.odin index 2384ada76..66f3c6547 100644 --- a/tests/internal/test_asan.odin +++ b/tests/internal/test_asan.odin @@ -1,42 +1,7 @@ // Intended to contain code that would trigger asan easily if the abi was set up badly. package test_asan -import "core:fmt" import "core:testing" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_12_bytes(&t) - test_12_bytes_two(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @(test) test_12_bytes :: proc(t: ^testing.T) { @@ -45,9 +10,9 @@ test_12_bytes :: proc(t: ^testing.T) { } a, b, ok := internal() - expect(t, a == max(f32), fmt.tprintf("a (%v) != max(f32)", a)) - expect(t, b == 0, fmt.tprintf("b (%v) != 0", b)) - expect(t, ok, fmt.tprintf("ok (%v) != true", ok)) + testing.expectf(t, a == max(f32), "a (%v) != max(f32)", a) + testing.expectf(t, b == 0, "b (%v) != 0", b) + testing.expectf(t, ok, "ok (%v) != true", ok) } @(test) @@ -57,6 +22,6 @@ test_12_bytes_two :: proc(t: ^testing.T) { } a, b := internal() - expect(t, a == 100., fmt.tprintf("a (%v) != 100.", a)) - expect(t, b == max(int), fmt.tprintf("b (%v) != max(int)", b)) + testing.expectf(t, a == 100., "a (%v) != 100.", a) + testing.expectf(t, b == max(int), "b (%v) != max(int)", b) } diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index 7d1dbf470..fa13244ac 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -1,26 +1,22 @@ package test_internal_map -import "core:fmt" +import "core:log" import "base:intrinsics" import "core:math/rand" -import "core:mem" -import "core:os" import "core:testing" -seed: u64 - ENTRY_COUNTS := []int{11, 101, 1_001, 10_001, 100_001, 1_000_001} @test map_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -65,12 +61,12 @@ map_insert_random_key_value :: proc(t: ^testing.T) { map_update_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_update_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -127,12 +123,12 @@ map_update_random_key_value :: proc(t: ^testing.T) { map_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k])) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k]) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] = %v", k, v)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] = %v", k, v) } else if m[k] != v { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } } @@ -205,12 +201,12 @@ map_delete_random_key_value :: proc(t: ^testing.T) { set_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Unexpected value. Expected m[%v] to exist", k) } } seed_incr += 1 @@ -252,12 +248,12 @@ set_insert_random_key_value :: proc(t: ^testing.T) { set_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted", k)) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted", k) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] to exist", k) } } } seed_incr += 1 } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - // Allow tests to be repeatable - SEED :: #config(SEED, -1) - when SEED > 0 { - seed = u64(SEED) - } else { - seed = u64(intrinsics.read_cycle_counter()) - } - fmt.println("Initialized seed to", seed) - - mem_track_test(&t, map_insert_random_key_value) - mem_track_test(&t, map_update_random_key_value) - mem_track_test(&t, map_delete_random_key_value) - - mem_track_test(&t, set_insert_random_key_value) - mem_track_test(&t, set_delete_random_key_value) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} +} \ No newline at end of file diff --git a/tests/internal/test_pow.odin b/tests/internal/test_pow.odin index 70b81258d..609087fb2 100644 --- a/tests/internal/test_pow.odin +++ b/tests/internal/test_pow.odin @@ -1,8 +1,7 @@ package test_internal_math_pow -import "core:fmt" +@(require) import "core:log" import "core:math" -import "core:os" import "core:testing" @test @@ -19,14 +18,14 @@ pow_test :: proc(t: ^testing.T) { // pow2_f64 returns the same float on all platforms because it isn't this stupid _v1 = 0h00000000_00000000 } - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f32(exp)) v2 := math.pow2_f32(exp) _v1 := transmute(u32)v1 _v2 := transmute(u32)v2 - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f16(exp)) @@ -36,46 +35,11 @@ pow_test :: proc(t: ^testing.T) { when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { if exp == -25 { - testing.logf(t, "skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") + log.info("skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") _v2 = 0 } } - - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2) } } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - pow_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} +} \ No newline at end of file diff --git a/tests/internal/test_rtti.odin b/tests/internal/test_rtti.odin index 12f64462b..ec5508208 100644 --- a/tests/internal/test_rtti.odin +++ b/tests/internal/test_rtti.odin @@ -1,11 +1,8 @@ package test_internal_rtti import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" - Buggy_Struct :: struct { a: int, b: bool, @@ -28,74 +25,22 @@ rtti_test :: proc(t: ^testing.T) { for v, i in g_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected g_b to be zero-initialized, got %v", g_b)) + testing.expectf(t, checksum == 0, "Expected g_b to be zero-initialized, got %v", g_b) } { checksum := 0 for v, i in l_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected l_b to be zero-initialized, got %v", l_b)) + testing.expectf(t, checksum == 0, "Expected l_b to be zero-initialized, got %v", l_b) } - expect(t, size_of(Buggy_Struct) == 40, fmt.tprintf("Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct))) - expect(t, size_of(g_buggy) == 40, fmt.tprintf("Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy))) - expect(t, size_of(l_buggy) == 40, fmt.tprintf("Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy))) + testing.expectf(t, size_of(Buggy_Struct) == 40, "Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct)) + testing.expectf(t, size_of(g_buggy) == 40, "Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy)) + testing.expectf(t, size_of(l_buggy) == 40, "Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy)) g_s := fmt.tprintf("%s", g_buggy) l_s := fmt.tprintf("%s", l_buggy) - expect(t, g_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)) - expect(t, l_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)) -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - mem_track_test(&t, rtti_test) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } + testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s) + testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s) } \ No newline at end of file diff --git a/tests/internal/test_string_compare.odin b/tests/internal/test_string_compare.odin index ff65b41c2..63b62cc96 100644 --- a/tests/internal/test_string_compare.odin +++ b/tests/internal/test_string_compare.odin @@ -1,7 +1,5 @@ package test_internal_string_compare -import "core:fmt" -import "core:os" import "core:testing" Op :: enum { Eq, Lt, Gt } @@ -29,65 +27,31 @@ string_compare :: proc(t: ^testing.T) { for res, op in v.res { switch op { case .Eq: - expect(t, (v.a == v.b) == res, fmt.tprintf("Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a == s_b) == res, fmt.tprintf("Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a == v.b) == res, "Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a == s_b) == res, "Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res) // If a == b then a != b - expect(t, (v.a != v.b) == !res, fmt.tprintf("Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res)) - expect(t, (s_a != s_b) == !res, fmt.tprintf("Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res)) + testing.expectf(t, (v.a != v.b) == !res, "Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res) + testing.expectf(t, (s_a != s_b) == !res, "Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res) case .Lt: - expect(t, (v.a < v.b) == res, fmt.tprintf("Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a < s_b) == res, fmt.tprintf("Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a < v.b) == res, "Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a < s_b) == res, "Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res) // .Lt | .Eq == .LtEq lteq := v.res[.Eq] | res - expect(t, (v.a <= v.b) == lteq, fmt.tprintf("Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq)) - expect(t, (s_a <= s_b) == lteq, fmt.tprintf("Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq)) + testing.expectf(t, (v.a <= v.b) == lteq, "Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq) + testing.expectf(t, (s_a <= s_b) == lteq, "Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq) case .Gt: - expect(t, (v.a > v.b) == res, fmt.tprintf("Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a > s_b) == res, fmt.tprintf("Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a > v.b) == res, "Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a > s_b) == res, "Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res) // .Gt | .Eq == .GtEq gteq := v.res[.Eq] | res - expect(t, (v.a >= v.b) == gteq, fmt.tprintf("Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq)) - expect(t, (s_a >= s_b) == gteq, fmt.tprintf("Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq)) + testing.expectf(t, (v.a >= v.b) == gteq, "Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq) + testing.expectf(t, (s_a >= s_b) == gteq, "Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq) } } } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - string_compare(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } } \ No newline at end of file diff --git a/tests/issues/run.bat b/tests/issues/run.bat index cd23faefb..f3e3daba9 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -3,19 +3,19 @@ if not exist "build\" mkdir build pushd build -set COMMON=-collection:tests=..\.. -define:ODIN_TEST_FANCY=false +set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style @echo on -..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2056.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b -..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b -..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2637.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2666.odin %COMMON% -file || exit /b +..\..\..\odin test ..\test_issue_829.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_1592.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2056.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2087.odin %COMMON% || exit /b +..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b +..\..\..\odin test ..\test_issue_2466.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2615.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2637.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2666.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index a7eee8514..24b388b07 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -4,22 +4,22 @@ set -eu mkdir -p build pushd build ODIN=../../../odin -COMMON="-collection:tests=../.. -define:ODIN_TEST_FANCY=false" +COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style" NO_NIL_ERR="Error: " set -x -$ODIN test ../test_issue_829.odin $COMMON -file -$ODIN test ../test_issue_1592.odin $COMMON -file -$ODIN test ../test_issue_2056.odin $COMMON -file -$ODIN test ../test_issue_2087.odin $COMMON -file -$ODIN build ../test_issue_2113.odin $COMMON -file -debug -$ODIN test ../test_issue_2466.odin $COMMON -file -$ODIN test ../test_issue_2615.odin $COMMON -file -$ODIN test ../test_issue_2637.odin $COMMON -file -$ODIN test ../test_issue_2666.odin $COMMON -file -if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then +$ODIN test ../test_issue_829.odin $COMMON +$ODIN test ../test_issue_1592.odin $COMMON +$ODIN test ../test_issue_2056.odin $COMMON +$ODIN test ../test_issue_2087.odin $COMMON +$ODIN build ../test_issue_2113.odin $COMMON -debug +$ODIN test ../test_issue_2466.odin $COMMON +$ODIN test ../test_issue_2615.odin $COMMON +$ODIN test ../test_issue_2637.odin $COMMON +$ODIN test ../test_issue_2666.odin $COMMON +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" diff --git a/tests/issues/test_issue_1592.odin b/tests/issues/test_issue_1592.odin index 800314a93..79eff33df 100644 --- a/tests/issues/test_issue_1592.odin +++ b/tests/issues/test_issue_1592.odin @@ -1,7 +1,6 @@ // Tests issue #1592 https://github.com/odin-lang/Odin/issues/1592 package test_issues -import "core:fmt" import "core:testing" /* Original issue #1592 example */ @@ -31,428 +30,428 @@ true_result :: proc() -> bool { @test test_simple_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (!CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !!CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == true { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == false { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == true) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == false) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_simple_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !!CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == true { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == false { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == true) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == false) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_false :: proc(t: ^testing.T) { if false_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !false_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_true :: proc(t: ^testing.T) { if true_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !true_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_const_false_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && !CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_false_const_true :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE || !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_false :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_TRUE || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_proc_false_const_false :: proc(t: ^testing.T) { if false_result() || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if false_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(false_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_false_const_true :: proc(t: ^testing.T) { if false_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if false_result() && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_false :: proc(t: ^testing.T) { if true_result() || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_true :: proc(t: ^testing.T) { if true_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(true_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } diff --git a/tests/issues/test_issue_2056.odin b/tests/issues/test_issue_2056.odin index 4869b557e..06bc11fba 100644 --- a/tests/issues/test_issue_2056.odin +++ b/tests/issues/test_issue_2056.odin @@ -1,7 +1,6 @@ // Tests issue #2056 https://github.com/odin-lang/Odin/issues/2056 package test_issues -import "core:fmt" import "core:testing" @test @@ -12,9 +11,9 @@ test_scalar_matrix_conversion :: proc(t: ^testing.T) { for i in 0..<4 { for j in 0..<4 { if i == j { - testing.expect(t, m[i,j] == 1, fmt.tprintf("expected 1 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 1, "expected 1 at m[%d,%d], found %f\n", i, j, m[i,j]) } else { - testing.expect(t, m[i,j] == 0, fmt.tprintf("expected 0 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 0, "expected 0 at m[%d,%d], found %f\n", i, j, m[i,j]) } } } diff --git a/tests/issues/test_issue_2466.odin b/tests/issues/test_issue_2466.odin index 4810cfea9..f5987903a 100644 --- a/tests/issues/test_issue_2466.odin +++ b/tests/issues/test_issue_2466.odin @@ -1,7 +1,6 @@ // Tests issue #2466 https://github.com/odin-lang/Odin/issues/2466 package test_issues -import "core:fmt" import "core:testing" Bug :: struct { @@ -16,7 +15,7 @@ test_compound_literal_local_reuse :: proc(t: ^testing.T) { val = v, arr = {42}, } - testing.expect(t, bug.val == 123, fmt.tprintf("expected 123, found %d", bug.val)) - testing.expect(t, bug.arr[0] == 42, fmt.tprintf("expected 42, found %d", bug.arr[0])) + testing.expectf(t, bug.val == 123, "expected 123, found %d", bug.val) + testing.expectf(t, bug.arr[0] == 42, "expected 42, found %d", bug.arr[0]) } diff --git a/tests/issues/test_issue_829.odin b/tests/issues/test_issue_829.odin index 273b3b3b5..229d8e9b4 100644 --- a/tests/issues/test_issue_829.odin +++ b/tests/issues/test_issue_829.odin @@ -1,7 +1,6 @@ // Tests issue #829 https://github.com/odin-lang/Odin/issues/829 package test_issues -import "core:fmt" import "core:testing" /* Original issue #829 example */ @@ -13,6 +12,6 @@ env : map[string]proc(a, b : int) -> int = { @(test) test_orig_ret :: proc(t: ^testing.T) { - r := fmt.tprint(env["+"](1, 2)) - testing.expect(t, r == "3", fmt.tprintf("%s: \"%s\" != \"3\"\n", #procedure, r)) -} + r := env["+"](1, 2) + testing.expectf(t, r == 3, "%q != 3", r) +} \ No newline at end of file diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 1ba53615a..84ab2f1ef 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -5,4 +5,4 @@ set PATH_TO_ODIN==..\..\odin echo --- echo Running vendor:glfw tests echo --- -%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file diff --git a/tests/vendor/glfw/test_vendor_glfw.odin b/tests/vendor/glfw/test_vendor_glfw.odin index ce55ad7ef..4254ebb09 100644 --- a/tests/vendor/glfw/test_vendor_glfw.odin +++ b/tests/vendor/glfw/test_vendor_glfw.odin @@ -1,49 +1,21 @@ package test_vendor_glfw import "core:testing" -import "core:fmt" import "vendor:glfw" -import "core:os" GLFW_MAJOR :: 3 GLFW_MINOR :: 4 GLFW_PATCH :: 0 -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - fmt.printf("[%v] ", loc) - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.println(message) - return - } - fmt.println(" PASS") - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_glfw(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @(test) test_glfw :: proc(t: ^testing.T) { major, minor, patch := glfw.GetVersion() - expect(t, major == GLFW_MAJOR && minor == GLFW_MINOR, fmt.tprintf("Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, major, minor, patch)) + testing.expectf( + t, + major == GLFW_MAJOR && \ + minor == GLFW_MINOR, + "Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", + GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, + major, minor, patch, + ) } From 8d8c42e9625b234e181dd8b7d16875ad5140d2ad Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:30:23 -0400 Subject: [PATCH 093/151] Use `T.seed` in tests where applicable --- tests/core/container/test_core_avl.odin | 4 ++-- tests/core/container/test_core_rbtree.odin | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/core/container/test_core_avl.odin b/tests/core/container/test_core_avl.odin index 37b21a6be..99dbba8b2 100644 --- a/tests/core/container/test_core_avl.odin +++ b/tests/core/container/test_core_avl.odin @@ -8,7 +8,7 @@ import "core:log" @(test) test_avl :: proc(t: ^testing.T) { - log.infof("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed) + log.infof("Testing avl using random seed %v.", t.seed) // Initialization. tree: avl.Tree(int) @@ -21,7 +21,7 @@ test_avl :: proc(t: ^testing.T) { testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") r: rand.Rand - rand.init(&r, random_seed) + rand.init(&r, t.seed) // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index a4151d120..8def8edb6 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -8,12 +8,6 @@ import "core:mem" import "core:slice" import "core:log" -@(private) -_RANDOM_SEED :: #config(RANDOM_SEED, u64(0)) - -// Exported -random_seed := u64(intrinsics.read_cycle_counter()) when _RANDOM_SEED == 0 else u64(_RANDOM_SEED) - test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) @@ -21,9 +15,9 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { context.allocator = mem.tracking_allocator(&track) r: rand.Rand - rand.init(&r, random_seed) + rand.init(&r, t.seed) - log.infof("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed) + log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed) tree: rb.Tree(Key, Value) rb.init(&tree) @@ -243,4 +237,4 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count) } // Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path, -// which guarantees that it has O(log n) height. \ No newline at end of file +// which guarantees that it has O(log n) height. From 3f1249c27e6364896d801cbbfe7edaf12f69cf37 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:34:13 -0400 Subject: [PATCH 094/151] Tell user about `ODIN_TEST_RANDOM_SEED` option --- core/testing/runner.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index fbe413bb2..5b80d0cf8 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -422,7 +422,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { "" if thread_count == 1 else "s") } - pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) + when SHARED_RANDOM_SEED == 0 { + pkg_log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed) + } else { + pkg_log.infof("The random seed sent to every test is: %v.", shared_random_seed) + } when TRACKING_MEMORY { when ALWAYS_REPORT_MEMORY { From f77ce359cec2cc50c5a98e4b4257e63ed702995e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:40:09 -0400 Subject: [PATCH 095/151] Be pedantic about not overwriting Odin errors I was encountering bounds-check error messages being overwritten during a test, if the test failed for another reason and sent a log message. The original intent of having this check inside of the above `if` block was that if a test sent an error message, then it was assumed an overwrite would be safe, but it's completely possible for a test to fail for a legitimate reason, then do an unrelated bounds check somewhere else that would be buried under the animation. This change will make sure that, no matter what, the progress display will not trigger a clear if a signal was raised. There's still no guarantee that bounds-check messages will be printed properly, and it's best to redirect STDERR. The only way that can be fixed is if they get a similar hook to `context.assertion_failure_proc`. --- core/testing/runner.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 5b80d0cf8..190e10744 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -648,10 +648,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator) pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) - when FANCY_OUTPUT { - signals_were_raised = true - bypass_progress_overwrite = true - } + } + + when FANCY_OUTPUT { + bypass_progress_overwrite = true + signals_were_raised = true } total_failure_count += 1 From f030603f0dc9c487221a8f65f5a1ccb7b345a237 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:47:46 -0400 Subject: [PATCH 096/151] Fix #3660 This also prevents a segfault if you do `odin build .odin -file` --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..80fc995cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2657,6 +2657,10 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return 1; } + if (!gb_file_exists(cast(const char*)init_filename.text)) { + gb_printf_err("The file '%.*s' was not found.\n", LIT(init_filename)); + return 1; + } } } } From ac9484206b8fb79bd289049ab8c2f4b6488e9cfa Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:15:28 -0400 Subject: [PATCH 097/151] Fix `STDIN`, `STDOUT`, `STDERR` handles for BSDs Tested on FreeBSD 14.0 and NetBSD 10.0 OpenBSD is untested, but link names were sourced from: https://github.com/openbsd/src/blob/master/include/stdio.h According to this, OpenBSD shares the same layout as NetBSD. FreeBSD has the same as Darwin in this regard. --- core/c/libc/stdio.odin | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index f17d3bd06..3e1d0f5a2 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + __sF: [3]FILE } + + stdin: ^FILE = &__sF[0] + stdout: ^FILE = &__sF[1] + stderr: ^FILE = &__sF[2] } when ODIN_OS == .FreeBSD { @@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + @(link_name="__stderrp") stderr: ^FILE + @(link_name="__stdinp") stdin: ^FILE + @(link_name="__stdoutp") stdout: ^FILE } } From c8539fe4115b733583849705bac14e396a3a7dd7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:28:07 -0400 Subject: [PATCH 098/151] Revert "Disable NetBSD tests until 'undefined reference to stdout' is solved." This reverts commit 21a1ddfbae92fb78bf536f4047016d0001b76cc2. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36914b41b..c5493775f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,10 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 + (cd tests/core; gmake all_bsd) + (cd tests/internal; gmake all_bsd) + (cd tests/issues; ./run.sh) + (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest From 6a5633df2d3c7a398d6aa20e1024f6f380c9d987 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 20:16:23 -0400 Subject: [PATCH 099/151] Fix wrong `PTHREAD_CANCEL_ASYNCHRONOUS` on FreeBSD and OpenBSD The test runner was deadlocking when a test raised a signal on FreeBSD. This is untested on OpenBSD, but I have referenced this file: https://github.com/openbsd/src/blob/master/include/pthread.h --- core/sys/unix/pthread_freebsd.odin | 4 ++-- core/sys/unix/pthread_openbsd.odin | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index 3417d3943..5f4dac289 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -95,7 +95,7 @@ sem_t :: struct { PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import "system:pthread" @@ -119,4 +119,4 @@ foreign pthread { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index 7ae82e662..855e7d99c 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -49,7 +49,7 @@ sem_t :: distinct rawptr PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import libc "system:c" @@ -71,4 +71,4 @@ foreign libc { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} From 7764ab2ab0a45786d3c27c2a5bfad264e181b7b7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:25:04 -0400 Subject: [PATCH 100/151] Prevent test runner deadlock on NetBSD Add `pthread_testcancel` to `core:sys/unix` --- core/sys/unix/pthread_unix.odin | 1 + core/testing/signal_handler_libc.odin | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 5760560ee..c876a214a 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -116,4 +116,5 @@ foreign pthread { pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int --- pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int --- + pthread_testcancel :: proc () --- } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index ff3dbe135..f60cf2540 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -6,6 +6,7 @@ import "base:intrinsics" import "core:c/libc" import "core:encoding/ansi" import "core:sync" +@require import "core:sys/unix" @(private="file") stop_runner_flag: libc.sig_atomic_t @@ -75,6 +76,18 @@ This is a dire bug and should be reported to the Odin developers. // Idle until this thread is terminated by the runner, // otherwise we may continue to generate signals. intrinsics.cpu_relax() + + when ODIN_OS != .Windows { + // NOTE(Feoramund): Some UNIX-like platforms may require this. + // + // During testing, I found that NetBSD 10.0 refused to + // terminate a task thread, even when its thread had been + // properly set to PTHREAD_CANCEL_ASYNCHRONOUS. + // + // The runner would stall after returning from `pthread_cancel`. + + unix.pthread_testcancel() + } } } } From fa29974dabc03479848935168eb4e26becbe304d Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:21:44 -0400 Subject: [PATCH 101/151] Use `Warning` log level for reporting memory leaks Works well with `-define:ODIN_TEST_LOG_LEVEL=warning`. --- core/testing/runner.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 190e10744..c82aa1fda 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -467,16 +467,18 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { when TRACKING_MEMORY { #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] + memory_is_in_bad_state := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + when ALWAYS_REPORT_MEMORY { should_report := true } else { - should_report := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + should_report := memory_is_in_bad_state } if should_report { write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) - pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + pkg_log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) bytes.buffer_reset(&batch_buffer) } From 0ff130d82bf058a41f77c08c8c0c63fb9e205a13 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:36:04 -0400 Subject: [PATCH 102/151] Fix ad hoc `printf` in test runner signal handler --- core/testing/signal_handler_libc.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index f60cf2540..d76fdd66b 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -51,7 +51,7 @@ stop_test_callback :: proc "c" (sig: libc.int) { sigbuf[i] = cast(u8)('0' + m) i -= 1 } - sigstr = cast(string)sigbuf[i:] + sigstr = cast(string)sigbuf[1 + i:len(sigbuf) - 1] } advisory_a := ` From 1617060f46d2467dff185c6c7c43ff8d7ed6e36e Mon Sep 17 00:00:00 2001 From: Laurent Dufresne Date: Mon, 3 Jun 2024 19:43:39 +0200 Subject: [PATCH 103/151] Fix type of two Win32 functions --- core/sys/windows/kernel32.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index eba275522..16b6fa244 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -453,9 +453,9 @@ foreign kernel32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilecompletionnotificationmodes) SetFileCompletionNotificationModes :: proc(FileHandle: HANDLE, Flags: u8) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-createiocompletionport) - CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ^uintptr, NumberOfConcurrentThreads: DWORD) -> HANDLE --- + CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) -> HANDLE --- //[MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus) - GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: uintptr, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- + GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: PULONG_PTR, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex) GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus) From 4e1dd4ced270ec374c9f45edec03dbc2e2e0b050 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:40:28 -0400 Subject: [PATCH 104/151] Move `Raw_Complex/Quaternion` types to `base:runtime` --- base/runtime/core.odin | 6 ++++++ core/mem/raw.odin | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 4b6a1949e..9b7a3f613 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -470,6 +470,12 @@ Raw_Soa_Pointer :: struct { index: int, } +Raw_Complex64 :: struct {real, imag: f32} +Raw_Complex128 :: struct {real, imag: f64} +Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} +Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} +Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} +Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} /* diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 56790e959..4f37ce5ab 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -11,13 +11,6 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array Raw_Map :: runtime.Raw_Map Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer -Raw_Complex64 :: struct {real, imag: f32} -Raw_Complex128 :: struct {real, imag: f64} -Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} -Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} -Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} -Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} - make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } From 97f1d12e042b1400a4af84447959ef17e1b57f4a Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:41:14 -0400 Subject: [PATCH 105/151] Add missing `Raw_*` types for complex and quaternion --- base/runtime/core.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 9b7a3f613..47b9a690c 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -470,10 +470,13 @@ Raw_Soa_Pointer :: struct { index: int, } +Raw_Complex32 :: struct {real, imag: f16} Raw_Complex64 :: struct {real, imag: f32} Raw_Complex128 :: struct {real, imag: f64} +Raw_Quaternion64 :: struct {imag, jmag, kmag: f16, real: f16} Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} +Raw_Quaternion64_Vector_Scalar :: struct {vector: [3]f16, scalar: f16} Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} From 88598c2c6483cdaa9d574d9883a65543d0d31676 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:45:32 -0400 Subject: [PATCH 106/151] Make use of `runtime.Raw_*` types in `core:math/linalg` --- core/math/linalg/general.odin | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 51dfd2360..37c0447cb 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -3,6 +3,7 @@ package linalg import "core:math" import "base:builtin" import "base:intrinsics" +import "base:runtime" // Generic @@ -223,33 +224,27 @@ quaternion_mul_quaternion :: proc "contextless" (q1, q2: $Q) -> Q where IS_QUATE @(require_results) quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V { - Raw_Quaternion :: struct {xyz: [3]f16, r: f16} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion64_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V { - Raw_Quaternion :: struct {xyz: [3]f32, r: f32} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion128_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V { - Raw_Quaternion :: struct {xyz: [3]f64, r: f64} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion256_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3} From 50b4a63fe141d8f2c8a17f61bfde8ce078daae14 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:02:35 +0100 Subject: [PATCH 107/151] Add `ast.Foreign_Impot_Decl.fullpaths` to walk.odin --- core/odin/ast/walk.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 63107a2e2..7304f237c 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -320,6 +320,7 @@ walk :: proc(v: ^Visitor, node: ^Node) { if n.comment != nil { walk(v, n.comment) } + walk_expr_list(v, n.fullpaths) case ^Proc_Group: walk_expr_list(v, n.args) From d7f6def8adf14c774b4ece317e510a321af23c64 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:18:27 -0400 Subject: [PATCH 108/151] Add aliases for `Raw_*` complex/quaternion types into `core:mem` --- core/mem/raw.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 4f37ce5ab..f56206957 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -11,6 +11,16 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array Raw_Map :: runtime.Raw_Map Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer +Raw_Complex32 :: runtime.Raw_Complex32 +Raw_Complex64 :: runtime.Raw_Complex64 +Raw_Complex128 :: runtime.Raw_Complex128 +Raw_Quaternion64 :: runtime.Raw_Quaternion64 +Raw_Quaternion128 :: runtime.Raw_Quaternion128 +Raw_Quaternion256 :: runtime.Raw_Quaternion256 +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar +Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar +Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar + make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } From 8e9716ea2f1628297b89e6d097b01bd574b1686c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:20:39 +0100 Subject: [PATCH 109/151] Add `ast.Foreign_Import_Decl` to `ast.clone` --- core/odin/ast/clone.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index bca740dd4..406add2e9 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -279,6 +279,7 @@ clone_node :: proc(node: ^Node) -> ^Node { r.body = clone(r.body) case ^Foreign_Import_Decl: r.name = auto_cast clone(r.name) + r.fullpaths = auto_cast clone_array(r.fullpaths) case ^Proc_Group: r.args = clone(r.args) case ^Attribute: From 4dac577caaafcee45695baf057795666ace7f431 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:21:54 +0100 Subject: [PATCH 110/151] Add attributes to Foreign_Import_Decl in `clone` --- core/odin/ast/clone.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index 406add2e9..b0a1673b2 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -278,8 +278,9 @@ clone_node :: proc(node: ^Node) -> ^Node { r.foreign_library = clone(r.foreign_library) r.body = clone(r.body) case ^Foreign_Import_Decl: + r.attributes = clone_dynamic_array(r.attributes) r.name = auto_cast clone(r.name) - r.fullpaths = auto_cast clone_array(r.fullpaths) + r.fullpaths = clone_array(r.fullpaths) case ^Proc_Group: r.args = clone(r.args) case ^Attribute: From 5b5106baee1ff76f8dfca28bc77ed0dfafeb4e53 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:36:54 +0100 Subject: [PATCH 111/151] Try updating nightly stuff --- .github/workflows/nightly.yml | 2 +- ci/upload_create_nightly.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c9a2c821b..cfd780380 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -189,7 +189,7 @@ jobs: DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | echo Authorizing B2 account - b2 authorize-account "$APPID" "$APPKEY" + b2 acount authorize "$APPID" "$APPKEY" echo Uploading artifcates to B2 chmod +x ./ci/upload_create_nightly.sh diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh index 065cb13bf..a4122f1da 100755 --- a/ci/upload_create_nightly.sh +++ b/ci/upload_create_nightly.sh @@ -22,4 +22,4 @@ else 7z a -bd "output/$filename" -r "$artifact" fi -b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename" +b2 upload-file "$bucket" "output/$filename" "nightly/$filename" From dc6a8e5ffbf86cd41b526d443be7a7f0a042b802 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:44:12 +0100 Subject: [PATCH 112/151] Fix typo --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index cfd780380..221ab1cdb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -189,7 +189,7 @@ jobs: DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | echo Authorizing B2 account - b2 acount authorize "$APPID" "$APPKEY" + b2 account authorize "$APPID" "$APPKEY" echo Uploading artifcates to B2 chmod +x ./ci/upload_create_nightly.sh From 0ef0894213f9b44e748bcdc965eedaaeb13dcc7f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:55:24 +0100 Subject: [PATCH 113/151] Fix to `bl.tok = path` --- core/odin/parser/parser.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 813585ba4..ad5ee9087 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1204,7 +1204,7 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { path := expect_token(p, .String) reserve(&fullpaths, 1) bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path)) - bl.tok = tok + bl.tok = path append(&fullpaths, bl) } From c8f9af64db1600b4efb61561215c7c9fc46fd940 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 23:01:39 +0100 Subject: [PATCH 114/151] Add `print` to `delete_old_binaries.py` --- ci/delete_old_binaries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 206d849f5..2a87bc731 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -12,6 +12,7 @@ def main(): print(f"Looking for binaries to delete older than {days_to_keep} days") files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n") + print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From 17225131f729de1e233220fabb7e1ea3257f02c3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 23:12:24 +0100 Subject: [PATCH 115/151] Try `/` rather than ` ` --- ci/delete_old_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 2a87bc731..294c3c4be 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -11,7 +11,7 @@ def main(): days_to_keep = int(sys.argv[2]) print(f"Looking for binaries to delete older than {days_to_keep} days") - files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long --versions {bucket}/nightly").split("\n") print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From f745a1c4709b6d38a7bc99da1ccf97a4790b2e73 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 00:22:49 +0200 Subject: [PATCH 116/151] b2 uri --- ci/create_nightly_json.py | 2 +- ci/delete_old_binaries.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index 2ad086a82..d7029399a 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -9,7 +9,7 @@ def main(): files_by_date = {} bucket = sys.argv[1] - files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n") for x in files_lines: parts = x.split(" ", 1) if parts[0]: diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 294c3c4be..39e8ff2ac 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -11,7 +11,7 @@ def main(): days_to_keep = int(sys.argv[2]) print(f"Looking for binaries to delete older than {days_to_keep} days") - files_lines = execute_cli(f"b2 ls --long --versions {bucket}/nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From d2a9122176c792f0445736639dba05b1817eb1f7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:48:09 -0400 Subject: [PATCH 117/151] Add `fmt` tests for printing complex and quaternion types --- tests/core/fmt/test_core_fmt.odin | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 82d009ac6..84b273f90 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -1,8 +1,10 @@ package test_core_fmt +import "base:runtime" import "core:fmt" import "core:os" import "core:testing" +import "core:math" import "core:mem" TEST_count := 0 @@ -61,6 +63,70 @@ test_fmt_memory :: proc(t: ^testing.T) { check(t, "0b", "%m", u8(0)) } +@(test) +test_fmt_complex_quaternion :: proc(t: ^testing.T) { + neg_inf := math.inf_f64(-1) + pos_inf := math.inf_f64(+1) + neg_zero := f64(0h80000000_00000000) + nan := math.nan_f64() + + // NOTE(Feoramund): Doing it this way, because complex construction is broken. + // Reported in issue #3665. + c: complex128 + cptr := cast(^runtime.Raw_Complex128)&c + + cptr^ = {0, 0} + check(t, "0+0i", "%v", c) + cptr^ = {1, 1} + check(t, "1+1i", "%v", c) + cptr^ = {1, 0} + check(t, "1+0i", "%v", c) + cptr^ = {-1, -1} + check(t, "-1-1i", "%v", c) + cptr^ = {0, neg_zero} + check(t, "0-0i", "%v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%v", c) + + // Check forced plus signs. + cptr^ = {0, neg_zero} + check(t, "+0-0i", "%+v", c) + cptr^ = {1, 1} + check(t, "+1+1i", "%+v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%+v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%+v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%+v", c) + + // Remember that the real number is the last in a quaternion's data layout, + // opposed to a complex, where it is the first. + q: quaternion256 + qptr := cast(^runtime.Raw_Quaternion256)&q + + qptr^ = {0, 0, 0, 0} + check(t, "0+0i+0j+0k", "%v", q) + qptr^ = {1, 1, 1, 1} + check(t, "1+1i+1j+1k", "%v", q) + qptr^ = {2, 3, 4, 1} + check(t, "1+2i+3j+4k", "%v", q) + qptr^ = {-1, -1, -1, -1} + check(t, "-1-1i-1j-1k", "%v", q) + qptr^ = {2, neg_zero, neg_zero, 1} + check(t, "1+2i-0j-0k", "%v", q) + qptr^ = {neg_inf, neg_inf, neg_inf, -1} + check(t, "-1-Infi-Infj-Infk", "%v", q) + qptr^ = {pos_inf, pos_inf, pos_inf, -1} + check(t, "-1+Infi+Infj+Infk", "%v", q) + qptr^ = {nan, nan, nan, -1} + check(t, "-1NaNiNaNjNaNk", "%v", q) +} + @(test) test_fmt_doc_examples :: proc(t: ^testing.T) { // C-like syntax From eb93779f630870b66103c559b07b855288dc8aa4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:49:38 -0400 Subject: [PATCH 118/151] Fix duplicate sign printing of complex and quaternion types Negative zero wasn't being detected (so it would appear as `+-0`), and `+Inf` was appearing as `++Inf` when imaginary. --- core/fmt/fmt.odin | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 62cd95968..f43683d11 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2,6 +2,7 @@ package fmt import "base:intrinsics" import "base:runtime" +import "core:math" import "core:math/bits" import "core:mem" import "core:io" @@ -2968,6 +2969,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { fmt_bit_field(fi, v, verb, info, "") } } +// This proc helps keep some of the code around whether or not to print an +// intermediate plus sign in complexes and quaternions more readable. +@(private) +_cq_should_print_intermediate_plus :: proc "contextless" (fi: ^Info, f: f64) -> bool { + if !fi.plus && f >= 0 { + #partial switch math.classify(f) { + case .Neg_Zero, .Inf: + // These two classes print their own signs. + return false + case: + return true + } + } + return false +} // Formats a complex number based on the given formatting verb // // Inputs: @@ -2981,7 +2997,7 @@ fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { case 'f', 'F', 'v', 'h', 'H', 'w': r, i := real(c), imag(c) fmt_float(fi, r, bits/2, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/2, verb) @@ -3007,19 +3023,19 @@ fmt_quaternion :: proc(fi: ^Info, q: quaternion256, bits: int, verb: rune) { fmt_float(fi, r, bits/4, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/4, verb) io.write_rune(fi.writer, 'i', &fi.n) - if !fi.plus && j >= 0 { + if _cq_should_print_intermediate_plus(fi, j) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, j, bits/4, verb) io.write_rune(fi.writer, 'j', &fi.n) - if !fi.plus && k >= 0 { + if _cq_should_print_intermediate_plus(fi, k) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, k, bits/4, verb) From e46e22e21ba61e0bceb2e96084c1e1772bb7ed11 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 4 Jun 2024 01:12:10 -0700 Subject: [PATCH 119/151] add a few more x11 funcs, fix egl bug --- vendor/egl/egl.odin | 2 +- vendor/x11/xlib/xlib_procs.odin | 2566 ++++++++++++++++--------------- 2 files changed, 1288 insertions(+), 1280 deletions(-) diff --git a/vendor/egl/egl.odin b/vendor/egl/egl.odin index 3174fa60b..82181b1c5 100644 --- a/vendor/egl/egl.odin +++ b/vendor/egl/egl.odin @@ -47,7 +47,7 @@ foreign egl { GetDisplay :: proc(display: NativeDisplayType) -> Display --- Initialize :: proc(display: Display, major: ^i32, minor: ^i32) -> i32 --- BindAPI :: proc(api: u32) -> i32 --- - ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Context, config_size: i32, num_config: ^i32) -> i32 --- + ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Config, config_size: i32, num_config: ^i32) -> i32 --- CreateWindowSurface :: proc(display: Display, config: Config, native_window: NativeWindowType, attrib_list: ^i32) -> Surface --- CreateContext :: proc(display: Display, config: Config, share_context: Context, attrib_list: ^i32) -> Context --- MakeCurrent :: proc(display: Display, draw: Surface, read: Surface, ctx: Context) -> i32 --- diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 5e999519b..5465cd71b 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -8,108 +8,108 @@ foreign xlib { /* ---- X11/Xlib.h ---------------------------------------------------------*/ -@(default_calling_convention="c") +@(default_calling_convention="c", link_prefix="X") foreign xlib { // Free data allocated by Xlib - XFree :: proc(ptr: rawptr) --- + Free :: proc(ptr: rawptr) --- // Opening/closing a display - XOpenDisplay :: proc(name: cstring) -> ^Display --- - XCloseDisplay :: proc(display: ^Display) --- - XSetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- + OpenDisplay :: proc(name: cstring) -> ^Display --- + CloseDisplay :: proc(display: ^Display) --- + SetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- // Generate a no-op request - XNoOp :: proc(display: ^Display) --- + NoOp :: proc(display: ^Display) --- // Display macros (connection) - XConnectionNumber :: proc(display: ^Display) -> i32 --- - XExtendedMaxRequestSize :: + ConnectionNumber :: proc(display: ^Display) -> i32 --- + ExtendedMaxRequestSize :: proc(display: ^Display) -> int --- - XMaxRequestSize :: proc(display: ^Display) -> int --- - XLastKnownRequestProcessed :: + MaxRequestSize :: proc(display: ^Display) -> int --- + LastKnownRequestProcessed :: proc(display: ^Display) -> uint --- - XNextRequest :: proc(display: ^Display) -> uint --- - XProtocolVersion :: proc(display: ^Display) -> i32 --- - XProtocolRevision :: proc(display: ^Display) -> i32 --- - XQLength :: proc(display: ^Display) -> i32 --- - XServerVendor :: proc(display: ^Display) -> cstring --- - XVendorRelease :: proc(display: ^Display) -> i32 --- + NextRequest :: proc(display: ^Display) -> uint --- + ProtocolVersion :: proc(display: ^Display) -> i32 --- + ProtocolRevision :: proc(display: ^Display) -> i32 --- + QLength :: proc(display: ^Display) -> i32 --- + ServerVendor :: proc(display: ^Display) -> cstring --- + VendorRelease :: proc(display: ^Display) -> i32 --- // Display macros (display properties) - XBlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XWhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- - XDisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- - XScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- - XDisplayString :: proc(display: ^Display) -> cstring --- + BlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- + WhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- + ListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- + DisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- + ScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- + DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) - XDefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - XDefaultDepth :: proc(display: ^Display) -> i32 --- - XDefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- - XDefaultRootWindow :: proc(display: ^Display) -> Window --- - XDefaultScreen :: proc(display: ^Display) -> i32 --- - XDefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- - XDefaultScreenOfDisplay :: + DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- + DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- + DefaultRootWindow :: proc(display: ^Display) -> Window --- + DefaultScreen :: proc(display: ^Display) -> i32 --- + DefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- + DefaultScreenOfDisplay :: proc(display: ^Display) -> ^Screen --- // Display macros (other) - XRootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- - XScreenCount :: proc(display: ^Display) -> i32 --- + RootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- + ScreenCount :: proc(display: ^Display) -> i32 --- // Display image format macros - XListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- - XImageByteOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapUnit :: proc(display: ^Display) -> i32 --- - XBitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapPad :: proc(display: ^Display) -> i32 --- - XDisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + ListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- + ImageByteOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapUnit :: proc(display: ^Display) -> i32 --- + BitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapPad :: proc(display: ^Display) -> i32 --- + DisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- // Screen macros - XBlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XWhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XCellsOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- - XDefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- - XDefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- - XDoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- - XDoesSaveUnders :: proc(screen: ^Screen) -> b32 --- - XDisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- - XScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- - XEventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- - XWidthOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightOfScreen :: proc(screen: ^Screen) -> i32 --- - XWidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XMaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XMinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XPlanesOfScreen :: proc(screen: ^Screen) -> i32 --- - XRootWindowOfScreen :: proc(screen: ^Screen) -> Window --- + BlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- + WhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- + CellsOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- + DefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- + DefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- + DoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- + DoesSaveUnders :: proc(screen: ^Screen) -> b32 --- + DisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- + ScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- + EventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- + WidthOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightOfScreen :: proc(screen: ^Screen) -> i32 --- + WidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- + MaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + MinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + PlanesOfScreen :: proc(screen: ^Screen) -> i32 --- + RootWindowOfScreen :: proc(screen: ^Screen) -> Window --- // Threading functions - XInitThreads :: proc() -> Status --- - XLockDisplay :: proc(display: ^Display) --- - XUnlockDisplay :: proc(display: ^Display) --- + InitThreads :: proc() -> Status --- + LockDisplay :: proc(display: ^Display) --- + UnlockDisplay :: proc(display: ^Display) --- // Internal connections - XAddConnectionWatch :: proc( + AddConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XRemoveConnectionWatch :: proc( + RemoveConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XProcessInternalConnections :: proc( + ProcessInternalConnections :: proc( display: ^Display, fd: i32, ) --- - XInternalConnectionNumbers :: proc( + InternalConnectionNumbers :: proc( display: ^Display, fds: ^[^]i32, count: ^i32, ) -> Status --- // Windows functions - XVisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- + VisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- // Windows: creation/destruction - XCreateWindow :: proc( + CreateWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -123,7 +123,7 @@ foreign xlib { attr_mask: WindowAttributeMask, attr: ^XSetWindowAttributes, ) -> Window --- - XCreateSimpleWindow :: proc( + CreateSimpleWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -134,34 +134,34 @@ foreign xlib { border: int, bg: int, ) -> Window --- - XDestroyWindow :: proc(display: ^Display, window: Window) --- - XDestroySubwindows :: proc(display: ^Display, window: Window) --- + DestroyWindow :: proc(display: ^Display, window: Window) --- + DestroySubwindows :: proc(display: ^Display, window: Window) --- // Windows: mapping/unmapping - XMapWindow :: proc(display: ^Display, window: Window) --- - XMapRaised :: proc(display: ^Display, window: Window) --- - XMapSubwindows :: proc(display: ^Display, window: Window) --- - XUnmapWindow :: proc(display: ^Display, window: Window) --- - XUnmapSubwindows :: proc(display: ^Display, window: Window) --- + MapWindow :: proc(display: ^Display, window: Window) --- + MapRaised :: proc(display: ^Display, window: Window) --- + MapSubwindows :: proc(display: ^Display, window: Window) --- + UnmapWindow :: proc(display: ^Display, window: Window) --- + UnmapSubwindows :: proc(display: ^Display, window: Window) --- // Windows: configuring - XConfigureWindow :: proc( + ConfigureWindow :: proc( display: ^Display, window: Window, mask: WindowChangesMask, values: XWindowChanges, ) --- - XMoveWindow :: proc( + MoveWindow :: proc( display: ^Display, window: Window, x: i32, y: i32, ) --- - XResizeWindow :: proc( + ResizeWindow :: proc( display: ^Display, window: Window, width: u32, height: u32, ) --- - XMoveResizeWindow :: proc( + MoveResizeWindow :: proc( display: ^Display, window: Window, x: i32, @@ -169,51 +169,51 @@ foreign xlib { width: u32, height: u32, ) --- - XSetWindowBorderWidth :: proc( + SetWindowBorderWidth :: proc( display: ^Display, window: Window, width: u32, ) --- // Window: changing stacking order - XRaiseWindow :: proc(display: ^Display, window: Window) --- - XLowerWindow :: proc(display: ^Display, window: Window) --- - XCirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- - XCirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- - XCirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- - XRestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- + RaiseWindow :: proc(display: ^Display, window: Window) --- + LowerWindow :: proc(display: ^Display, window: Window) --- + CirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- + CirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- + CirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- + RestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- // Window: changing attributes - XChangeWindowAttributes :: proc( + ChangeWindowAttributes :: proc( display: ^Display, window: Window, attr_mask: WindowAttributeMask, attr: XWindowAttributes, ) --- - XSetWindowBackground :: proc( + SetWindowBackground :: proc( display: ^Display, window: Window, pixel: uint, ) --- - XSetWindowBackgroundMap :: proc( + SetWindowBackgroundMap :: proc( display: ^Display, window: Window, pixmap: Pixmap, ) --- - XSetWindowColormap :: proc( + SetWindowColormap :: proc( display: ^Display, window: Window, colormap: Colormap, ) --- - XDefineCursor :: proc( + DefineCursor :: proc( display: ^Display, window: Window, cursor: Cursor, ) --- - XUndefineCursor :: proc( + UndefineCursor :: proc( display: ^Display, window: Window, ) --- // Windows: querying information - XQueryTree :: proc( + QueryTree :: proc( display: ^Display, window: Window, root: ^Window, @@ -221,12 +221,12 @@ foreign xlib { children: ^[^]Window, nchildren: ^u32, ) -> Status --- - XGetWindowAttributes :: proc( + GetWindowAttributes :: proc( display: ^Display, window: Window, attr: ^XWindowAttributes, ) --- - XGetGeometry :: proc( + GetGeometry :: proc( display: ^Display, drawable: Drawable, root: ^Window, @@ -238,7 +238,7 @@ foreign xlib { depth: ^u32, ) -> Status --- // Windows: translating screen coordinates - XTranslateCoordinates :: proc( + TranslateCoordinates :: proc( display: ^Display, src_window: Window, dst_window: Window, @@ -247,7 +247,7 @@ foreign xlib { dst_x: ^i32, dst_y: ^i32, ) -> b32 --- - XQueryPointer :: proc( + QueryPointer :: proc( display: ^Display, window: Window, root: ^Window, @@ -259,29 +259,29 @@ foreign xlib { mask: ^KeyMask, ) -> b32 --- // Atoms - XInternAtom :: proc( + InternAtom :: proc( display: ^Display, name: cstring, existing: b32, ) -> Atom --- - XInternAtoms :: proc( + InternAtoms :: proc( display: ^Display, names: [^]cstring, count: i32, atoms: [^]Atom, ) -> Status --- - XGetAtomName :: proc( + GetAtomName :: proc( display: ^Display, atom: Atom, ) -> cstring --- - XGetAtomNames :: proc( + GetAtomNames :: proc( display: ^Display, atoms: [^]Atom, count: i32, names: [^]cstring, ) -> Status --- // Windows: Obtaining and changing properties - XGetWindowProperty :: proc( + GetWindowProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -295,12 +295,12 @@ foreign xlib { bytes_after: [^]uint, props: ^rawptr, ) -> i32 --- - XListProperties :: proc( + ListProperties :: proc( display: ^Display, window: Window, num: ^i32, ) -> [^]Atom --- - XChangeProperty :: proc( + ChangeProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -310,30 +310,30 @@ foreign xlib { data: rawptr, count: i32, ) --- - XRotateWindowProperties :: proc( + RotateWindowProperties :: proc( display: ^Display, window: Window, props: [^]Atom, nprops: i32, npos: i32, ) --- - XDeleteProperty :: proc( + DeleteProperty :: proc( display: ^Display, window: Window, prop: Atom, ) --- // Selections - XSetSelectionOwner :: proc( + SetSelectionOwner :: proc( display: ^Display, selection: Atom, owber: Window, time: Time, ) --- - XGetSelectionOwner :: proc( + GetSelectionOwner :: proc( display: ^Display, selection: Atom, ) -> Window --- - XConvertSelection :: proc( + ConvertSelection :: proc( display: ^Display, selection: Atom, target: Atom, @@ -342,23 +342,23 @@ foreign xlib { time: Time, ) --- // Creating and freeing pixmaps - XCreatePixmap :: proc( + CreatePixmap :: proc( display: ^Display, drawable: Drawable, width: u32, height: u32, depth: u32, ) -> Pixmap --- - XFreePixmap :: proc( + FreePixmap :: proc( display: ^Display, pixmap: Pixmap, ) --- // Creating recoloring and freeing cursors - XCreateFontCursor :: proc( + CreateFontCursor :: proc( display: ^Display, shape: CursorShape, ) -> Cursor --- - XCreateGlyphCursor :: proc( + CreateGlyphCursor :: proc( display: ^Display, src_font: Font, mask_font: Font, @@ -367,7 +367,7 @@ foreign xlib { fg: ^XColor, bg: ^XColor, ) -> Cursor --- - XCreatePixmapCursor :: proc( + CreatePixmapCursor :: proc( display: ^Display, source: Pixmap, mask: Pixmap, @@ -376,7 +376,7 @@ foreign xlib { x: u32, y: u32, ) -> Cursor --- - XQueryBestCursor :: proc( + QueryBestCursor :: proc( display: ^Display, drawable: Drawable, width: u32, @@ -384,72 +384,50 @@ foreign xlib { out_width: ^u32, out_height: ^u32, ) -> Status --- - XRecolorCursor :: proc( + RecolorCursor :: proc( display: ^Display, cursor: Cursor, fg: ^XColor, bg: ^XColor, ) --- - XFreeCursor :: proc(display: ^Display, cursor: Cursor) --- + FreeCursor :: proc(display: ^Display, cursor: Cursor) --- // Creation/destruction of colormaps - XCreateColormap :: proc( + CreateColormap :: proc( display: ^Display, window: Window, visual: ^Visual, alloc: ColormapAlloc, ) -> Colormap --- - XCopyColormapAndFree :: proc( + CopyColormapAndFree :: proc( display: ^Display, colormap: Colormap, ) -> Colormap --- - XFreeColormap :: proc( + FreeColormap :: proc( display: ^Display, colormap: Colormap, ) --- // Mapping color names to values - XLookupColor :: proc( + LookupColor :: proc( display: ^Display, colomap: Colormap, name: cstring, exact: ^XColor, screen: ^XColor, ) -> Status --- - XcmsLookupColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - exact: XcmsColor, - screen: XcmsColor, - format: XcmsColorFormat, - ) -> Status --- // Allocating and freeing color cells - XAllocColor :: proc( + AllocColor :: proc( display: ^Display, colormap: Colormap, screen: ^XColor, ) -> Status --- - XcmsAllocColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocNamedColor :: proc( + AllocNamedColor :: proc( display: ^Display, colormap: Colormap, name: cstring, screen: ^XColor, exact: ^XColor, ) -> Status --- - XcmsAllocNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - screen: ^XcmsColor, - exact: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocColorCells :: proc( + AllocColorCells :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -458,7 +436,7 @@ foreign xlib { pixels: [^]uint, npixels: u32, ) -> Status --- - XAllocColorPlanes :: proc( + AllocColorPlanes :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -471,7 +449,7 @@ foreign xlib { gmask: [^]uint, bmask: [^]uint, ) -> Status --- - XFreeColors :: proc( + FreeColors :: proc( display: ^Display, colormap: Colormap, pixels: [^]uint, @@ -479,17 +457,1155 @@ foreign xlib { planes: uint, ) --- // Modifying and querying colormap cells - XStoreColor :: proc( + StoreColor :: proc( display: ^Display, colormap: Colormap, color: ^XColor, ) --- - XStoreColors :: proc( + StoreColors :: proc( display: ^Display, colormap: Colormap, color: [^]XColor, ncolors: i32, ) --- + // Graphics context functions + CreateGC :: proc( + display: ^Display, + drawable: Drawable, + mask: GCAttributeMask, + attr: ^XGCValues, + ) -> GC --- + CopyGC :: proc( + display: ^Display, + src: GC, + dst: GC, + mask: GCAttributeMask, + ) --- + ChangeGC :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) --- + GetGCValues :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) -> Status --- + FreeGC :: proc(display: ^Display, gc: GC) --- + GCContextFromGC :: proc(gc: GC) -> GContext --- + FlushGC :: proc(display: ^Display, gc: GC) --- + // Convenience routines for GC + SetState :: proc( + display: ^Display, + gc: GC, + fg: uint, + bg: uint, + fn: GCFunction, + pmask: uint, + ) --- + SetForeground :: proc( + display: ^Display, + gc: GC, + fg: uint, + ) --- + SetBackground :: proc( + display: ^Display, + gc: GC, + bg: uint, + ) --- + SetFunction :: proc( + display: ^Display, + gc: GC, + fn: GCFunction, + ) --- + SetPlaneMask :: proc( + display: ^Display, + gc: GC, + pmask: uint, + ) --- + SetLineAttributes :: proc( + display: ^Display, + gc: GC, + width: u32, + line_style: LineStyle, + cap_style: CapStyle, + join_style: JoinStyle, + ) --- + SetDashes :: proc( + display: ^Display, + gc: GC, + dash_offs: i32, + dash_list: [^]i8, + n: i32, + ) --- + SetFillStyle :: proc( + display: ^Display, + gc: GC, + style: FillStyle, + ) --- + SetFillRule :: proc( + display: ^Display, + gc: GC, + rule: FillRule, + ) --- + QueryBestSize :: proc( + display: ^Display, + class: i32, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestTile :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestStripple :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: u32, + out_height: u32, + ) -> Status --- + SetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- + SetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- + SetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetFont :: proc(display: ^Display, gc: GC, font: Font) --- + SetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- + SetClipRectangles :: proc( + display: ^Display, + gc: GC, + x: i32, + y: i32, + rects: [^]XRectangle, + n: i32, + ordering: i32, + ) --- + SetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- + SetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- + SetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- + // Graphics functions + ClearArea :: proc( + display: ^Display, + window: Window, + x: i32, + y: i32, + width: u32, + height: u32, + exp: b32, + ) --- + ClearWindow :: proc( + display: ^Display, + window: Window, + ) --- + CopyArea :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + ) --- + CopyPlane :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + plane: uint, + ) --- + // Drawing lines, points, rectangles and arc + DrawPoint :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + ) --- + DrawPoints :: proc( + display: Display, + drawable: Drawable, + gc: GC, + point: [^]XPoint, + npoints: i32, + mode: CoordMode, + ) --- + DrawLine :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x1: i32, + y1: i32, + x2: i32, + y2: i32, + ) --- + DrawLines :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + ) --- + DrawSegments :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + segs: [^]XSegment, + nsegs: i32, + ) --- + DrawRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + DrawRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + DrawArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + DrawArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Filling areas + FillRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + FillRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + FillPolygon :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + shape: Shape, + mode: CoordMode, + ) --- + FillArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + FillArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Font metrics + LoadFont :: proc(display: ^Display, name: cstring) -> Font --- + QueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- + LoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- + FreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- + GetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- + UnloadFont :: proc(display: ^Display, font: Font) --- + ListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- + FreeFontNames :: proc(display: ^Display, list: [^]cstring) --- + ListFontsWithInfo :: proc( + display: ^Display, + pat: cstring, + max: i32, + count: ^i32, + info: ^[^]XFontStruct, + ) -> [^]cstring --- + FreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- + // Computing character string sizes + TextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- + TextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- + TextExtents :: proc( + font_struct: ^XFontStruct, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + TextExtents16 :: proc( + font_struct: ^XFontStruct, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents :: proc( + display: ^Display, + font_id: XID, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents16 :: proc( + display: ^Display, + font_id: XID, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + // Drawing complex text + DrawText :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem, + nitems: i32, + ) --- + DrawText16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem16, + nitems: i32, + ) --- + // Drawing text characters + DrawString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + DrawImageString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawImageString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + // Transferring images between client and server + InitImage :: proc(image: ^XImage) -> Status --- + PutImage :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + image: ^XImage, + src_x: i32, + src_y: i32, + dst_x: i32, + dst_y: i32, + width: u32, + height: u32, + ) --- + GetImage :: proc( + display: ^Display, + drawable: Drawable, + x: i32, + y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + ) -> ^XImage --- + GetSubImage :: proc( + display: ^Display, + drawable: Drawable, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + dst: ^XImage, + dst_x: i32, + dst_y: i32, + ) -> ^XImage --- + // Window and session manager functions + ReparentWindow :: proc( + display: ^Display, + window: Window, + parent: Window, + x: i32, + y: i32, + ) --- + ChangeSaveSet :: proc( + display: ^Display, + window: Window, + mode: SaveSetChangeMode, + ) --- + AddToSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + RemoveFromSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + // Managing installed colormaps + InstallColormap :: proc(display: ^Display, colormap: Colormap) --- + UninstallColormap :: proc(display: ^Display, colormap: Colormap) --- + ListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- + // Setting and retrieving font search paths + SetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- + GetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- + FreeFontPath :: proc(list: [^]cstring) --- + // Grabbing the server + GrabServer :: proc(display: ^Display) --- + UngrabServer :: proc(display: ^Display) --- + // Killing clients + KillClient :: proc(display: ^Display, resource: XID) --- + // Controlling the screen saver + SetScreenSaver :: proc( + display: ^Display, + timeout: i32, + interval: i32, + blanking: ScreenSaverBlanking, + exposures: ScreenSavingExposures, + ) --- + ForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- + ActivateScreenSaver :: proc(display: ^Display) --- + ResetScreenSaver :: proc(display: ^Display) --- + GetScreenSaver :: proc( + display: ^Display, + timeout: ^i32, + interval: ^i32, + blanking: ^ScreenSaverBlanking, + exposures: ^ScreenSavingExposures, + ) --- + // Controlling host address + AddHost :: proc(display: ^Display, addr: ^XHostAddress) --- + AddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + ListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- + RemoveHost :: proc(display: ^Display, host: XHostAddress) --- + RemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + // Access control list + SetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- + EnableAccessControl :: proc(display: ^Display) --- + DisableAccessControl :: proc(display: ^Display) --- + // Events + SelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- + Flush :: proc(display: ^Display) --- + Sync :: proc(display: ^Display) --- + EventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- + Pending :: proc(display: ^Display) -> i32 --- + NextEvent :: proc(display: ^Display, event: ^XEvent) --- + PeekEvent :: proc(display: ^Display, event: ^XEvent) --- + GetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- + FreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- + // Selecting events using a predicate procedure + IfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + CheckIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + arg: rawptr, + ) -> b32 --- + PeekIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + // Selecting events using a window or event mask + WindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) --- + CheckWindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + MaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) --- + CheckMaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + CheckTypedEvent :: proc( + display: ^Display, + type: EventType, + event: ^XEvent, + ) -> b32 --- + CheckTypedWindowEvent :: proc( + display: ^Display, + window: Window, + type: EventType, + event: ^XEvent, + ) -> b32 --- + // Putting events back + PutBackEvent :: proc( + display: ^Display, + event: ^XEvent, + ) --- + // Sending events to other applications + SendEvent :: proc( + display: ^Display, + window: Window, + propagate: b32, + mask: EventMask, + event: ^XEvent, + ) -> Status --- + // Getting the history of pointer motion + DisplayMotionBufferSize :: proc(display: ^Display) -> uint --- + GetMotionEvents :: proc( + display: ^Display, + window: Window, + start: Time, + stop: Time, + nevents: ^i32, + ) -> [^]XTimeCoord --- + // Enabling or disabling synchronization + SetAfterFunction :: proc( + display: ^Display, + procedure: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + Synchronize :: proc( + display: ^Display, + onoff: b32, + ) -> i32 --- + // Error handling + SetErrorHandler :: proc( + handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, + ) -> i32 --- + GetErrorText :: proc( + display: ^Display, + code: i32, + buffer: [^]u8, + size: i32, + ) --- + GetErrorDatabaseText :: proc( + display: ^Display, + name: cstring, + message: cstring, + default_string: cstring, + buffer: [^]u8, + size: i32, + ) --- + DisplayName :: proc(string: cstring) -> cstring --- + SetIOErrorHandler :: proc( + handler: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + // Pointer grabbing + GrabPointer :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + time: Time, + ) -> i32 --- + UngrabPointer :: proc( + display: ^Display, + time: Time, + ) -> i32 --- + ChangeActivePointerGrab :: proc( + display: ^Display, + event_mask: EventMask, + cursor: Cursor, + time: Time, + ) --- + GrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + event_mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + ) --- + UngrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + ) --- + GrabKeyboard :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + time: Time, + ) -> i32 --- + UngrabKeyboard :: proc( + display: ^Display, + time: Time, + ) --- + GrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + ) --- + UngrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + ) --- + // Resuming event processing + AllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- + // Moving the pointer + WarpPointer :: proc( + display: ^Display, + src_window: Window, + dst_window: Window, + src_x: i32, + src_y: i32, + src_width: u32, + src_height: u32, + dst_x: i32, + dst_y: i32, + ) --- + // Controlling input focus + SetInputFocus :: proc( + display: ^Display, + focus: Window, + revert_to: FocusRevert, + time: Time, + ) --- + GetInputFocus :: proc( + display: ^Display, + focus: ^Window, + revert_to: ^FocusRevert, + ) --- + // Manipulating the keyboard and pointer settings + ChangeKeyboardControl :: proc( + display: ^Display, + mask: KeyboardControlMask, + values: ^XKeyboardControl, + ) --- + GetKeyboardControl :: proc( + display: ^Display, + values: ^XKeyboardState, + ) --- + AutoRepeatOn :: proc(display: ^Display) --- + AutoRepeatOff :: proc(display: ^Display) --- + Bell :: proc(display: ^Display, percent: i32) --- + QueryKeymap :: proc(display: ^Display, keys: [^]u32) --- + SetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + GetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + ChangePointerControl :: proc( + display: ^Display, + do_accel: b32, + do_threshold: b32, + accel_numerator: i32, + accel_denominator: i32, + threshold: i32, + ) --- + GetPointerControl :: proc( + display: ^Display, + accel_numerator: ^i32, + accel_denominator: ^i32, + threshold: ^i32, + ) --- + // Manipulating the keyboard encoding + DisplayKeycodes :: proc( + display: ^Display, + min_keycodes: ^i32, + max_keycodes: ^i32, + ) --- + GetKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + count: i32, + keysyms_per: ^i32, + ) -> ^KeySym --- + ChangeKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + keysyms_per: i32, + keysyms: [^]KeySym, + num_codes: i32, + ) --- + NewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- + InsertModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + DeleteModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + FreeModifiermap :: proc(modmap: ^XModifierKeymap) --- + SetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- + GetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- + // Manipulating top-level windows + IconifyWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + WithdrawWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + ReconfigureWMWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + mask: WindowChangesMask, + changes: ^XWindowChanges, + ) -> Status --- + // Getting and setting the WM_NAME property + SetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + StoreName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + FetchName :: proc( + display: ^Display, + window: Window, + name: ^cstring, + ) -> Status --- + SetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetIconName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + GetIconName :: proc( + display: ^Display, + window: Window, + prop: ^cstring, + ) -> Status --- + // Setting and reading WM_HINTS property + AllocWMHints :: proc() -> ^XWMHints --- + SetWMHints :: proc( + display: ^Display, + window: Window, + hints: ^XWMHints, + ) --- + GetWMHints :: proc( + display: ^Display, + window: Window, + ) -> ^XWMHints --- + // Setting and reading MW_NORMAL_HINTS property + AllocSizeHints :: proc() -> ^XSizeHints --- + SetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + ) --- + GetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + flags: ^SizeHints, + ) -> Status --- + SetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + prop: Atom, + ) --- + GetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + masks: ^SizeHints, + prop: Atom, + ) -> Status --- + // Setting and reading the WM_CLASS property + AllocClassHint :: proc() -> ^XClassHint --- + SetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) --- + GetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) -> Status --- + // Setting and reading WM_TRANSIENT_FOR property + SetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: Window, + ) --- + GetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: ^Window, + ) -> Status --- + // Setting and reading the WM_PROTOCOLS property + SetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: [^]Atom, + count: i32, + ) -> Status --- + GetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: ^[^]Atom, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_COLORMAP_WINDOWS property + SetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: [^]Window, + count: i32, + ) -> Status --- + GetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: ^[^]Window, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_ICON_SIZE_PROPERTY + AllocIconSize :: proc() -> ^XIconSize --- + SetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: [^]XIconSize, + count: i32, + ) --- + GetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: ^[^]XIconSize, + count: ^i32, + ) -> Status --- + // Using window manager convenience functions + mbSetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: cstring, + icon_name: cstring, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XClassHint, + ) --- + SetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: ^XTextProperty, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XWMHints, + ) --- + // Client to session manager communication + SetCommand :: proc( + display: ^Display, + window: Window, + argv: [^]cstring, + argc: i32, + ) --- + GetCommand :: proc( + display: ^Display, + window: Window, + argv: ^[^]cstring, + argc: ^i32, + ) -> Status --- + SetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^XStandardColormap, + prop: Atom, + ) --- + GetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^[^]XStandardColormap, + count: ^i32, + prop: Atom, + ) -> Status --- + // Keyboard utility functions + LookupKeysym :: proc( + event: ^XKeyEvent, + index: i32, + ) -> KeySym --- + KeycodeToKeysym :: proc( + display: ^Display, + keycode: KeyCode, + index: i32, + ) -> KeySym --- + KeysymToKeycode :: proc( + display: ^Display, + keysym: KeySym, + ) -> KeyCode --- + RefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- + ConvertCase :: proc( + keysym: KeySym, + lower: ^KeySym, + upper: ^KeySym, + ) --- + StringToKeysym :: proc(str: cstring) -> KeySym --- + KeysymToString :: proc(keysym: KeySym) -> cstring --- + LookupString :: proc( + event: ^XKeyEvent, + buffer: [^]u8, + count: i32, + keysym: ^KeySym, + status: ^XComposeStatus, + ) -> i32 --- + RebindKeysym :: proc( + display: ^Display, + keysym: KeySym, + list: [^]KeySym, + mod_count: i32, + string: [^]u8, + num_bytes: i32, + ) --- + // Allocating permanent storage + Permalloc :: proc(size: u32) -> rawptr --- + // Parsing the window geometry + ParseGeometry :: proc( + parsestring: cstring, + x_ret: ^i32, + y_ret: ^i32, + width: ^u32, + height: ^u32, + ) -> i32 --- + WMGeometry :: proc( + display: ^Display, + screen_no: i32, + user_geom: cstring, + def_geom: cstring, + bwidth: u32, + hints: ^XSizeHints, + x_ret: ^i32, + y_ret: ^i32, + w_ret: ^u32, + h_ret: ^u32, + grav: ^Gravity, + ) -> i32 --- + // Creating, copying and destroying regions + CreateRegion :: proc() -> Region --- + PolygonRegion :: proc( + points: [^]XPoint, + n: i32, + fill: FillRule, + ) -> Region --- + SetRegion :: proc( + display: ^Display, + gc: GC, + region: Region, + ) --- + DestroyRegion :: proc(r: Region) --- + // Moving or shrinking regions + OffsetRegion :: proc(region: Region, dx, dy: i32) --- + ShrinkRegion :: proc(region: Region, dx, dy: i32) --- + // Computing with regions + ClipBox :: proc(region: Region, rect: ^XRectangle) --- + IntersectRegion :: proc(sra, srb, ret: Region) --- + UnionRegion :: proc(sra, srb, ret: Region) --- + UnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- + SubtractRegion :: proc(sra, srb, ret: Region) --- + XorRegion :: proc(sra, srb, ret: Region) --- + EmptyRegion :: proc(reg: Region) -> b32 --- + EqualRegion :: proc(a,b: Region) -> b32 --- + PointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- + RectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- + // Using cut buffers + StoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- + StoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- + FetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- + FetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- + // Determining the appropriate visual types + GetVisualInfo :: proc( + display: ^Display, + mask: VisualInfoMask, + info: ^XVisualInfo, + nret: ^i32, + ) -> [^]XVisualInfo --- + MatchVisualInfo :: proc( + display: ^Display, + screen_no: i32, + depth: i32, + class: i32, + ret: ^XVisualInfo, + ) -> Status --- + // Manipulating images + CreateImage :: proc( + display: ^Display, + visual: ^Visual, + depth: u32, + format: ImageFormat, + offset: i32, + data: rawptr, + width: u32, + height: u32, + pad: i32, + stride: i32, + ) -> ^XImage --- + GetPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + ) -> uint --- + PutPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + pixel: uint, + ) --- + SubImage :: proc( + image: ^XImage, + x: i32, + y: i32, + w: u32, + h: u32, + ) -> ^XImage --- + AddPixel :: proc( + image: ^XImage, + value: int, + ) --- + DestroyImage :: proc(image: ^XImage) --- + ResourceManagerString :: proc(display: ^Display) -> cstring --- +} + +@(default_calling_convention="c") +foreign xlib { + XcmsLookupColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + exact: XcmsColor, + screen: XcmsColor, + format: XcmsColorFormat, + ) -> Status --- XcmsStoreColor :: proc( display: ^Display, colormap: Colormap, @@ -502,25 +1618,25 @@ foreign xlib { ncolors: XcmsColor, cflags: [^]b32, ) -> Status --- - XStoreNamedColor :: proc( + StoreNamedColor :: proc( display: ^Display, colormap: Colormap, name: cstring, pixel: uint, flags: ColorFlags, ) --- - XQueryColor :: proc( + QueryColor :: proc( display: ^Display, colormap: Colormap, color: ^XColor, ) --- - XQueryColors :: proc( + QueryColors :: proc( display: ^Display, colormap: Colormap, colors: [^]XColor, ncolors: i32, ) --- - XQueryExtension :: proc( + QueryExtension :: proc( display: ^Display, name: cstring, major_opcode_return: ^i32, @@ -790,1129 +1906,21 @@ foreign xlib { chroma: XcmsFloat, color: ^XcmsColor, ) -> Status --- - // Graphics context functions - XCreateGC :: proc( + XcmsAllocNamedColor :: proc( display: ^Display, - drawable: Drawable, - mask: GCAttributeMask, - attr: ^XGCValues, - ) -> GC --- - XCopyGC :: proc( - display: ^Display, - src: GC, - dst: GC, - mask: GCAttributeMask, - ) --- - XChangeGC :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, - ) --- - XGetGCValues :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, + colormap: Colormap, + name: cstring, + screen: ^XcmsColor, + exact: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XFreeGC :: proc(display: ^Display, gc: GC) --- - XGCContextFromGC :: proc(gc: GC) -> GContext --- - XFlushGC :: proc(display: ^Display, gc: GC) --- - // Convenience routines for GC - XSetState :: proc( - display: ^Display, - gc: GC, - fg: uint, - bg: uint, - fn: GCFunction, - pmask: uint, - ) --- - XSetForeground :: proc( - display: ^Display, - gc: GC, - fg: uint, - ) --- - XSetBackground :: proc( - display: ^Display, - gc: GC, - bg: uint, - ) --- - XSetFunction :: proc( - display: ^Display, - gc: GC, - fn: GCFunction, - ) --- - XSetPlaneMask :: proc( - display: ^Display, - gc: GC, - pmask: uint, - ) --- - XSetLineAttributes :: proc( - display: ^Display, - gc: GC, - width: u32, - line_style: LineStyle, - cap_style: CapStyle, - join_style: JoinStyle, - ) --- - XSetDashes :: proc( - display: ^Display, - gc: GC, - dash_offs: i32, - dash_list: [^]i8, - n: i32, - ) --- - XSetFillStyle :: proc( - display: ^Display, - gc: GC, - style: FillStyle, - ) --- - XSetFillRule :: proc( - display: ^Display, - gc: GC, - rule: FillRule, - ) --- - XQueryBestSize :: proc( - display: ^Display, - class: i32, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, + XcmsAllocColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XQueryBestTile :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, - ) -> Status --- - XQueryBestStripple :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: u32, - out_height: u32, - ) -> Status --- - XSetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- - XSetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- - XSetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetFont :: proc(display: ^Display, gc: GC, font: Font) --- - XSetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- - XSetClipRectangles :: proc( - display: ^Display, - gc: GC, - x: i32, - y: i32, - rects: [^]XRectangle, - n: i32, - ordering: i32, - ) --- - XSetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- - XSetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- - XSetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- - // Graphics functions - XClearArea :: proc( - display: ^Display, - window: Window, - x: i32, - y: i32, - width: u32, - height: u32, - exp: b32, - ) --- - XClearWindow :: proc( - display: ^Display, - window: Window, - ) --- - XCopyArea :: proc( - display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - ) --- - XCopyPlane :: proc( - display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - plane: uint, - ) --- - // Drawing lines, points, rectangles and arc - XDrawPoint :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - ) --- - XDrawPoints :: proc( - display: Display, - drawable: Drawable, - gc: GC, - point: [^]XPoint, - npoints: i32, - mode: CoordMode, - ) --- - XDrawLine :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x1: i32, - y1: i32, - x2: i32, - y2: i32, - ) --- - XDrawLines :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - ) --- - XDrawSegments :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - segs: [^]XSegment, - nsegs: i32, - ) --- - XDrawRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XDrawRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XDrawArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XDrawArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Filling areas - XFillRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XFillRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XFillPolygon :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - shape: Shape, - mode: CoordMode, - ) --- - XFillArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XFillArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Font metrics - XLoadFont :: proc(display: ^Display, name: cstring) -> Font --- - XQueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- - XLoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- - XFreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- - XGetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- - XUnloadFont :: proc(display: ^Display, font: Font) --- - XListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- - XFreeFontNames :: proc(display: ^Display, list: [^]cstring) --- - XListFontsWithInfo :: proc( - display: ^Display, - pat: cstring, - max: i32, - count: ^i32, - info: ^[^]XFontStruct, - ) -> [^]cstring --- - XFreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- - // Computing character string sizes - XTextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- - XTextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- - XTextExtents :: proc( - font_struct: ^XFontStruct, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XTextExtents16 :: proc( - font_struct: ^XFontStruct, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents :: proc( - display: ^Display, - font_id: XID, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents16 :: proc( - display: ^Display, - font_id: XID, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - // Drawing complex text - XDrawText :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem, - nitems: i32, - ) --- - XDrawText16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem16, - nitems: i32, - ) --- - // Drawing text characters - XDrawString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - XDrawImageString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawImageString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - // Transferring images between client and server - XInitImage :: proc(image: ^XImage) -> Status --- - XPutImage :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - image: ^XImage, - src_x: i32, - src_y: i32, - dst_x: i32, - dst_y: i32, - width: u32, - height: u32, - ) --- - XGetImage :: proc( - display: ^Display, - drawable: Drawable, - x: i32, - y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - ) -> ^XImage --- - XGetSubImage :: proc( - display: ^Display, - drawable: Drawable, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - dst: ^XImage, - dst_x: i32, - dst_y: i32, - ) -> ^XImage --- - // Window and session manager functions - XReparentWindow :: proc( - display: ^Display, - window: Window, - parent: Window, - x: i32, - y: i32, - ) --- - XChangeSaveSet :: proc( - display: ^Display, - window: Window, - mode: SaveSetChangeMode, - ) --- - XAddToSaveSet :: proc( - display: ^Display, - window: Window, - ) --- - XRemoveFromSaveSet :: proc( - display: ^Display, - window: Window, - ) --- - // Managing installed colormaps - XInstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XUninstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- - // Setting and retrieving font search paths - XSetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- - XGetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- - XFreeFontPath :: proc(list: [^]cstring) --- - // Grabbing the server - XGrabServer :: proc(display: ^Display) --- - XUngrabServer :: proc(display: ^Display) --- - // Killing clients - XKillClient :: proc(display: ^Display, resource: XID) --- - // Controlling the screen saver - XSetScreenSaver :: proc( - display: ^Display, - timeout: i32, - interval: i32, - blanking: ScreenSaverBlanking, - exposures: ScreenSavingExposures, - ) --- - XForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- - XActivateScreenSaver :: proc(display: ^Display) --- - XResetScreenSaver :: proc(display: ^Display) --- - XGetScreenSaver :: proc( - display: ^Display, - timeout: ^i32, - interval: ^i32, - blanking: ^ScreenSaverBlanking, - exposures: ^ScreenSavingExposures, - ) --- - // Controlling host address - XAddHost :: proc(display: ^Display, addr: ^XHostAddress) --- - XAddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - XListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- - XRemoveHost :: proc(display: ^Display, host: XHostAddress) --- - XRemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - // Access control list - XSetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- - XEnableAccessControl :: proc(display: ^Display) --- - XDisableAccessControl :: proc(display: ^Display) --- - // Events - XSelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- - XFlush :: proc(display: ^Display) --- - XSync :: proc(display: ^Display) --- - XEventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- - XPending :: proc(display: ^Display) -> i32 --- - XNextEvent :: proc(display: ^Display, event: ^XEvent) --- - XPeekEvent :: proc(display: ^Display, event: ^XEvent) --- - XGetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- - XFreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- - // Selecting events using a predicate procedure - XIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - XCheckIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - arg: rawptr, - ) -> b32 --- - XPeekIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - // Selecting events using a window or event mask - XWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XCheckTypedEvent :: proc( - display: ^Display, - type: EventType, - event: ^XEvent, - ) -> b32 --- - XCheckTypedWindowEvent :: proc( - display: ^Display, - window: Window, - type: EventType, - event: ^XEvent, - ) -> b32 --- - // Putting events back - XPutBackEvent :: proc( - display: ^Display, - event: ^XEvent, - ) --- - // Sending events to other applications - XSendEvent :: proc( - display: ^Display, - window: Window, - propagate: b32, - mask: EventMask, - event: ^XEvent, - ) -> Status --- - // Getting the history of pointer motion - XDisplayMotionBufferSize :: proc(display: ^Display) -> uint --- - XGetMotionEvents :: proc( - display: ^Display, - window: Window, - start: Time, - stop: Time, - nevents: ^i32, - ) -> [^]XTimeCoord --- - // Enabling or disabling synchronization - XSetAfterFunction :: proc( - display: ^Display, - procedure: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - XSynchronize :: proc( - display: ^Display, - onoff: b32, - ) -> i32 --- - // Error handling - XSetErrorHandler :: proc( - handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, - ) -> i32 --- - XGetErrorText :: proc( - display: ^Display, - code: i32, - buffer: [^]u8, - size: i32, - ) --- - XGetErrorDatabaseText :: proc( - display: ^Display, - name: cstring, - message: cstring, - default_string: cstring, - buffer: [^]u8, - size: i32, - ) --- - XDisplayName :: proc(string: cstring) -> cstring --- - XSetIOErrorHandler :: proc( - handler: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - // Pointer grabbing - XGrabPointer :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - time: Time, - ) -> i32 --- - XUngrabPointer :: proc( - display: ^Display, - time: Time, - ) -> i32 --- - XChangeActivePointerGrab :: proc( - display: ^Display, - event_mask: EventMask, - cursor: Cursor, - time: Time, - ) --- - XGrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - event_mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - ) --- - XUngrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - ) --- - XGrabKeyboard :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - time: Time, - ) -> i32 --- - XUngrabKeyboard :: proc( - display: ^Display, - time: Time, - ) --- - XGrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - ) --- - XUngrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - ) --- - // Resuming event processing - XAllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- - // Moving the pointer - XWarpPointer :: proc( - display: ^Display, - src_window: Window, - dst_window: Window, - src_x: i32, - src_y: i32, - src_width: u32, - src_height: u32, - dst_x: i32, - dst_y: i32, - ) --- - // Controlling input focus - XSetInputFocus :: proc( - display: ^Display, - focus: Window, - revert_to: FocusRevert, - time: Time, - ) --- - XGetInputFocus :: proc( - display: ^Display, - focus: ^Window, - revert_to: ^FocusRevert, - ) --- - // Manipulating the keyboard and pointer settings - XChangeKeyboardControl :: proc( - display: ^Display, - mask: KeyboardControlMask, - values: ^XKeyboardControl, - ) --- - XGetKeyboardControl :: proc( - display: ^Display, - values: ^XKeyboardState, - ) --- - XAutoRepeatOn :: proc(display: ^Display) --- - XAutoRepeatOff :: proc(display: ^Display) --- - XBell :: proc(display: ^Display, percent: i32) --- - XQueryKeymap :: proc(display: ^Display, keys: [^]u32) --- - XSetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XGetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XChangePointerControl :: proc( - display: ^Display, - do_accel: b32, - do_threshold: b32, - accel_numerator: i32, - accel_denominator: i32, - threshold: i32, - ) --- - XGetPointerControl :: proc( - display: ^Display, - accel_numerator: ^i32, - accel_denominator: ^i32, - threshold: ^i32, - ) --- - // Manipulating the keyboard encoding - XDisplayKeycodes :: proc( - display: ^Display, - min_keycodes: ^i32, - max_keycodes: ^i32, - ) --- - XGetKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - count: i32, - keysyms_per: ^i32, - ) -> ^KeySym --- - XChangeKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - keysyms_per: i32, - keysyms: [^]KeySym, - num_codes: i32, - ) --- - XNewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- - XInsertModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XDeleteModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XFreeModifiermap :: proc(modmap: ^XModifierKeymap) --- - XSetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- - XGetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- - // Manipulating top-level windows - XIconifyWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XWithdrawWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XReconfigureWMWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - mask: WindowChangesMask, - changes: ^XWindowChanges, - ) -> Status --- - // Getting and setting the WM_NAME property - XSetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XStoreName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XFetchName :: proc( - display: ^Display, - window: Window, - name: ^cstring, - ) -> Status --- - XSetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetIconName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XGetIconName :: proc( - display: ^Display, - window: Window, - prop: ^cstring, - ) -> Status --- - // Setting and reading WM_HINTS property - XAllocWMHints :: proc() -> ^XWMHints --- - XSetWMHints :: proc( - display: ^Display, - window: Window, - hints: ^XWMHints, - ) --- - XGetWMHints :: proc( - display: ^Display, - window: Window, - ) -> ^XWMHints --- - // Setting and reading MW_NORMAL_HINTS property - XAllocSizeHints :: proc() -> ^XSizeHints --- - XSetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - ) --- - XGetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - flags: ^SizeHints, - ) -> Status --- - XSetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - prop: Atom, - ) --- - XGetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - masks: ^SizeHints, - prop: Atom, - ) -> Status --- - // Setting and reading the WM_CLASS property - XAllocClassHint :: proc() -> ^XClassHint --- - XSetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) --- - XGetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) -> Status --- - // Setting and reading WM_TRANSIENT_FOR property - XSetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: Window, - ) --- - XGetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: ^Window, - ) -> Status --- - // Setting and reading the WM_PROTOCOLS property - XSetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: [^]Atom, - count: i32, - ) -> Status --- - XGetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: ^[^]Atom, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_COLORMAP_WINDOWS property - XSetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: [^]Window, - count: i32, - ) -> Status --- - XGetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: ^[^]Window, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_ICON_SIZE_PROPERTY - XAllocIconSize :: proc() -> ^XIconSize --- - XSetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: [^]XIconSize, - count: i32, - ) --- - XGetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: ^[^]XIconSize, - count: ^i32, - ) -> Status --- - // Using window manager convenience functions - XmbSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: cstring, - icon_name: cstring, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XClassHint, - ) --- - XSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: ^XTextProperty, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XWMHints, - ) --- - // Client to session manager communication - XSetCommand :: proc( - display: ^Display, - window: Window, - argv: [^]cstring, - argc: i32, - ) --- - XGetCommand :: proc( - display: ^Display, - window: Window, - argv: ^[^]cstring, - argc: ^i32, - ) -> Status --- - XSetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^XStandardColormap, - prop: Atom, - ) --- - XGetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^[^]XStandardColormap, - count: ^i32, - prop: Atom, - ) -> Status --- - // Keyboard utility functions - XLookupKeysym :: proc( - event: ^XKeyEvent, - index: i32, - ) -> KeySym --- - XKeycodeToKeysym :: proc( - display: ^Display, - keycode: KeyCode, - index: i32, - ) -> KeySym --- - XKeysymToKeycode :: proc( - display: ^Display, - keysym: KeySym, - ) -> KeyCode --- - XRefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- - XConvertCase :: proc( - keysym: KeySym, - lower: ^KeySym, - upper: ^KeySym, - ) --- - XStringToKeysym :: proc(str: cstring) -> KeySym --- - XKeysymToString :: proc(keysym: KeySym) -> cstring --- - XLookupString :: proc( - event: ^XKeyEvent, - buffer: [^]u8, - count: i32, - keysym: ^KeySym, - status: ^XComposeStatus, - ) -> i32 --- - XRebindKeysym :: proc( - display: ^Display, - keysym: KeySym, - list: [^]KeySym, - mod_count: i32, - string: [^]u8, - num_bytes: i32, - ) --- - // Allocating permanent storage - XPermalloc :: proc(size: u32) -> rawptr --- - // Parsing the window geometry - XParseGeometry :: proc( - parsestring: cstring, - x_ret: ^i32, - y_ret: ^i32, - width: ^u32, - height: ^u32, - ) -> i32 --- - XWMGeometry :: proc( - display: ^Display, - screen_no: i32, - user_geom: cstring, - def_geom: cstring, - bwidth: u32, - hints: ^XSizeHints, - x_ret: ^i32, - y_ret: ^i32, - w_ret: ^u32, - h_ret: ^u32, - grav: ^Gravity, - ) -> i32 --- - // Creating, copying and destroying regions - XCreateRegion :: proc() -> Region --- - XPolygonRegion :: proc( - points: [^]XPoint, - n: i32, - fill: FillRule, - ) -> Region --- - XSetRegion :: proc( - display: ^Display, - gc: GC, - region: Region, - ) --- - XDestroyRegion :: proc(r: Region) --- - // Moving or shrinking regions - XOffsetRegion :: proc(region: Region, dx, dy: i32) --- - XShrinkRegion :: proc(region: Region, dx, dy: i32) --- - // Computing with regions - XClipBox :: proc(region: Region, rect: ^XRectangle) --- - XIntersectRegion :: proc(sra, srb, ret: Region) --- - XUnionRegion :: proc(sra, srb, ret: Region) --- - XUnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- - XSubtractRegion :: proc(sra, srb, ret: Region) --- - XXorRegion :: proc(sra, srb, ret: Region) --- - XEmptyRegion :: proc(reg: Region) -> b32 --- - XEqualRegion :: proc(a,b: Region) -> b32 --- - XPointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- - XRectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- - // Using cut buffers - XStoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- - XStoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- - XFetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- - XFetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- - // Determining the appropriate visual types - XGetVisualInfo :: proc( - display: ^Display, - mask: VisualInfoMask, - info: ^XVisualInfo, - nret: ^i32, - ) -> [^]XVisualInfo --- - XMatchVisualInfo :: proc( - display: ^Display, - screen_no: i32, - depth: i32, - class: i32, - ret: ^XVisualInfo, - ) -> Status --- - // Manipulating images - XCreateImage :: proc( - display: ^Display, - visual: ^Visual, - depth: u32, - format: ImageFormat, - offset: i32, - data: rawptr, - width: u32, - height: u32, - pad: i32, - stride: i32, - ) -> ^XImage --- - XGetPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - ) -> uint --- - XPutPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - pixel: uint, - ) --- - XSubImage :: proc( - image: ^XImage, - x: i32, - y: i32, - w: u32, - h: u32, - ) -> ^XImage --- - XAddPixel :: proc( - image: ^XImage, - value: int, - ) --- - XDestroyImage :: proc(image: ^XImage) --- + XrmInitialize :: proc() --- + XrmGetStringDatabase :: proc(data: cstring) -> XrmDatabase --- + XrmGetResource :: proc(db: XrmDatabase, name: cstring, class: cstring, type_return: ^cstring, val_return: ^XrmValue) -> b32 --- } From 303d86ab7e54abb084bb96a556b6ff6d4a2d0f63 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 11:26:57 +0200 Subject: [PATCH 120/151] Complete nightly scripts update to new b2 cli version --- ci/create_nightly_json.py | 5 ++--- ci/delete_old_binaries.py | 6 ++---- ci/upload_create_nightly.sh | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index d7029399a..03c653040 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -13,7 +13,7 @@ def main(): for x in files_lines: parts = x.split(" ", 1) if parts[0]: - json_str = execute_cli(f"b2 get-file-info {parts[0]}") + json_str = execute_cli(f"b2 file info {parts[0]}") data = json.loads(json_str) name = remove_prefix(data['fileName'], "nightly/") url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" @@ -47,5 +47,4 @@ def execute_cli(command): return sb.stdout.read().decode("utf-8"); if __name__ == '__main__': - sys.exit(main()) - + sys.exit(main()) \ No newline at end of file diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 39e8ff2ac..6ea3cb78d 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -12,7 +12,6 @@ def main(): print(f"Looking for binaries to delete older than {days_to_keep} days") files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") - print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] @@ -23,7 +22,7 @@ def main(): if delta.days > days_to_keep: print(f'Deleting {parts[5]}') - execute_cli(f'b2 delete-file-version {parts[0]}') + execute_cli(f'b2 rm {parts[0]}') def execute_cli(command): @@ -31,5 +30,4 @@ def execute_cli(command): return sb.stdout.read().decode("utf-8"); if __name__ == '__main__': - sys.exit(main()) - + sys.exit(main()) \ No newline at end of file diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh index a4122f1da..8404b33ff 100755 --- a/ci/upload_create_nightly.sh +++ b/ci/upload_create_nightly.sh @@ -22,4 +22,4 @@ else 7z a -bd "output/$filename" -r "$artifact" fi -b2 upload-file "$bucket" "output/$filename" "nightly/$filename" +b2 file upload "$bucket" "output/$filename" "nightly/$filename" From 52ba3357ee75fbaaf8676fe15307c148eddeb16c Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 4 Jun 2024 04:48:24 -0700 Subject: [PATCH 121/151] oops, missed shuffling a few x-funcs --- vendor/x11/xlib/xlib_procs.odin | 59 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 5465cd71b..735ddc9c4 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -1592,6 +1592,31 @@ foreign xlib { image: ^XImage, value: int, ) --- + StoreNamedColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + pixel: uint, + flags: ColorFlags, + ) --- + QueryColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XColor, + ) --- + QueryColors :: proc( + display: ^Display, + colormap: Colormap, + colors: [^]XColor, + ncolors: i32, + ) --- + QueryExtension :: proc( + display: ^Display, + name: cstring, + major_opcode_return: ^i32, + first_event_return: ^i32, + first_error_return: ^i32, + ) -> b32 --- DestroyImage :: proc(image: ^XImage) --- ResourceManagerString :: proc(display: ^Display) -> cstring --- } @@ -1618,31 +1643,6 @@ foreign xlib { ncolors: XcmsColor, cflags: [^]b32, ) -> Status --- - StoreNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - pixel: uint, - flags: ColorFlags, - ) --- - QueryColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XColor, - ) --- - QueryColors :: proc( - display: ^Display, - colormap: Colormap, - colors: [^]XColor, - ncolors: i32, - ) --- - QueryExtension :: proc( - display: ^Display, - name: cstring, - major_opcode_return: ^i32, - first_event_return: ^i32, - first_error_return: ^i32, - ) -> b32 --- XcmsQueryColor :: proc( display: ^Display, colormap: Colormap, @@ -1669,12 +1669,9 @@ foreign xlib { // Color conversion context macros XcmsDisplayOfCCC :: proc(ccc: XcmsCCC) -> ^Display --- XcmsVisualOfCCC :: proc(ccc: XcmsCCC) -> ^Visual --- - XcmsScreenNumberOfCCC :: - proc(ccc: XcmsCCC) -> i32 --- - XcmsScreenWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- - XcmsClientWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsScreenNumberOfCCC :: proc(ccc: XcmsCCC) -> i32 --- + XcmsScreenWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsClientWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- // Modifying the attributes of color conversion context XcmsSetWhitePoint :: proc( ccc: XcmsCCC, From 7d293898348173f4a8c373c037b3035f83f9dd13 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 14:21:51 +0200 Subject: [PATCH 122/151] fix debug info IR error on LLVM < 13 --- src/llvm_backend_debug.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 2654a1d28..afbf3e046 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -46,6 +46,15 @@ gb_internal LLVMMetadataRef lb_debug_end_location_from_ast(lbProcedure *p, Ast * return lb_debug_location_from_token_pos(p, ast_end_token(node).pos); } +gb_internal void lb_debug_file_line(lbModule *m, Ast *node, LLVMMetadataRef *file, unsigned *line) { + if (*file == nullptr) { + if (node) { + *file = lb_get_llvm_metadata(m, node->file()); + *line = cast(unsigned)ast_token(node).pos.line; + } + } +} + gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) { i64 size = type_size_of(type); // Check size gb_unused(size); @@ -117,6 +126,8 @@ gb_internal LLVMMetadataRef lb_debug_basic_struct(lbModule *m, String const &nam gb_internal LLVMMetadataRef lb_debug_struct(lbModule *m, Type *type, Type *bt, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { GB_ASSERT(bt->kind == Type_Struct); + lb_debug_file_line(m, bt->Struct.node, &file, &line); + unsigned tag = DW_TAG_structure_type; if (is_type_raw_union(bt)) { tag = DW_TAG_union_type; @@ -336,6 +347,8 @@ gb_internal LLVMMetadataRef lb_debug_union(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); + lb_debug_file_line(m, bt->Union.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -415,6 +428,8 @@ gb_internal LLVMMetadataRef lb_debug_bitset(lbModule *m, Type *type, String name Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_BitSet); + lb_debug_file_line(m, bt->BitSet.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -494,6 +509,8 @@ gb_internal LLVMMetadataRef lb_debug_enum(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Enum); + lb_debug_file_line(m, bt->Enum.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); From 606608c02bb7271717245c0c66c302d5aa046477 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:49:17 +0100 Subject: [PATCH 123/151] Utilize `foreign import` constant strings for foreign imports --- vendor/raylib/raygui.odin | 42 ++++-------------- vendor/raylib/raylib.odin | 89 ++++++++++----------------------------- vendor/raylib/rlgl.odin | 19 +++------ 3 files changed, 36 insertions(+), 114 deletions(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 41a4250a1..18f1a9c51 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -5,47 +5,21 @@ import "core:c" RAYGUI_SHARED :: #config(RAYGUI_SHARED, false) when ODIN_OS == .Windows { - when RAYGUI_SHARED { - foreign import lib { - "windows/rayguidll.lib", - } - } else { - foreign import lib { - "windows/raygui.lib", - } + foreign import lib { + "windows/rayguidll.lib" when RAYGUI_SHARED else "windows/raygui.lib", } } else when ODIN_OS == .Linux { - when RAYGUI_SHARED { - foreign import lib "linux/libraygui.so" - } else { - foreign import lib "linux/libraygui.a" + foreign import lib { + "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a" } } else when ODIN_OS == .Darwin { when ODIN_ARCH == .arm64 { - when RAYGUI_SHARED { - foreign import lib { - "macos-arm64/libraygui.dylib", - } - } else { - foreign import lib { - "macos-arm64/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos-arm64/libraygui.dylib" when RAYGUI_SHARED else "macos-arm64/libraygui.a", } } else { - when RAYGUI_SHARED { - foreign import lib { - "macos/libraygui.dylib", - } - } else { - foreign import lib { - "macos/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos/libraygui.dylib" when RAYGUI_SHARED else "macos/libraygui.a", } } } else { diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 1d9451b3d..730575a2c 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -97,76 +97,33 @@ MAX_TEXT_BUFFER_LENGTH :: #config(RAYLIB_MAX_TEXT_BUFFER_LENGTH, 1024) RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) when ODIN_OS == .Windows { - when RAYLIB_SHARED { - @(extra_linker_flags="/NODEFAULTLIB:msvcrt") - foreign import lib { - "windows/raylibdll.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } - } else { - @(extra_linker_flags="/NODEFAULTLIB:libcmt") - foreign import lib { - "windows/raylib.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } + _WINDOWS_LIB :: "windows/raylib" +" dll" when RAYLIB_SHARED else "" + ".lib" + @(extra_linker_flags="/NODEFAULTLIB:msvcrt") + foreign import lib { + "windows/raylibdll.lib" when RAYLIB_SHARED else "windows/raylib.lib" , + "system:Winmm.lib", + "system:Gdi32.lib", + "system:User32.lib", + "system:Shell32.lib", } } else when ODIN_OS == .Linux { - when RAYLIB_SHARED { - foreign import lib { - // Note(bumbread): I'm not sure why in `linux/` folder there are - // multiple copies of raylib.so, but since these bindings are for - // particular version of the library, I better specify it. Ideally, - // though, it's best specified in terms of major (.so.4) - "linux/libraylib.so.500", - "system:dl", - "system:pthread", - } - } else { - foreign import lib { - "linux/libraylib.a", - "system:dl", - "system:pthread", - } + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "linux/libraylib.so.500" when RAYLIB_SHARED else "linux/libraylib.a", + "system:dl", + "system:pthread", } } else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - when RAYLIB_SHARED { - foreign import lib { - "macos-arm64/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } - } else { - when RAYLIB_SHARED { - foreign import lib { - "macos/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } + foreign import lib { + "macos" + + "-arm64" when ODIN_ARCH == .arm64 else "" + + "/libraylib" + ".500.dylib" when RAYLIB_SHARED else ".a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", } } else { foreign import lib "system:raylib" diff --git a/vendor/raylib/rlgl.odin b/vendor/raylib/rlgl.odin index 97ab0fd07..c9e8c28c2 100644 --- a/vendor/raylib/rlgl.odin +++ b/vendor/raylib/rlgl.odin @@ -122,20 +122,11 @@ when ODIN_OS == .Windows { } else when ODIN_OS == .Linux { foreign import lib "linux/libraylib.a" } else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } + foreign import lib { + "macos-arm64/libraylib.a" when ODIN_ARCH == .arm64 else "macos/libraylib.a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", } } else { foreign import lib "system:raylib" From 72ce111a95cdddd60c90ada285d19e3c4e9e41f8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:51:19 +0100 Subject: [PATCH 124/151] Keep `-vet` happy --- vendor/raylib/raygui.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 18f1a9c51..ff528ea7b 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -10,7 +10,7 @@ when ODIN_OS == .Windows { } } else when ODIN_OS == .Linux { foreign import lib { - "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a" + "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a", } } else when ODIN_OS == .Darwin { when ODIN_ARCH == .arm64 { From 11b1a48bf04f61edf5587fa627e78fb525da6090 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:54:57 +0100 Subject: [PATCH 125/151] Fix `extra_linker_flags` for raylib on windows --- vendor/raylib/raylib.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 730575a2c..6204f0bde 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -97,8 +97,7 @@ MAX_TEXT_BUFFER_LENGTH :: #config(RAYLIB_MAX_TEXT_BUFFER_LENGTH, 1024) RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) when ODIN_OS == .Windows { - _WINDOWS_LIB :: "windows/raylib" +" dll" when RAYLIB_SHARED else "" + ".lib" - @(extra_linker_flags="/NODEFAULTLIB:msvcrt") + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) foreign import lib { "windows/raylibdll.lib" when RAYLIB_SHARED else "windows/raylib.lib" , "system:Winmm.lib", From 6b386631ddd7305f3111310a2ed3f921d5035298 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 16:16:27 +0100 Subject: [PATCH 126/151] Improve error message suggestion for passing enums to integers --- src/check_expr.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2e008fe93..6af80eed5 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -125,6 +125,8 @@ gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish); +gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -2252,6 +2254,17 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0) { if (is_type_integer(type) && o->value.kind == ExactValue_Integer) { gbString b = type_to_string(type); + defer (gb_string_free(b)); + + if (is_type_enum(o->type)) { + if (check_is_castable_to(c, o, type)) { + gbString ot = type_to_string(o->type); + error_line("\tSuggestion: Try casting the '%s' expression to '%s'", ot, b); + gb_string_free(ot); + } + return true; + } + i64 sz = type_size_of(type); i64 bit_size = 8*sz; @@ -2301,7 +2314,6 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, } } - gb_string_free(b); return true; } From 3b7100f8e5cdf325a51926ba02f785a5443b0415 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 16:17:49 +0100 Subject: [PATCH 127/151] raygui: change `c.int` to their correct enum types --- vendor/raylib/raygui.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index ff528ea7b..8cda9c072 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -30,8 +30,8 @@ RAYGUI_VERSION :: "4.0" // Style property GuiStyleProp :: struct { - controlId: u16, - propertyId: u16, + controlId: u16, + propertyId: u16, propertyValue: c.int, } @@ -200,7 +200,7 @@ GuiColorPickerProperty :: enum c.int { HUEBAR_SELECTOR_OVERFLOW, // ColorPicker right hue bar selector overflow } -SCROLLBAR_LEFT_SIDE :: 0 +SCROLLBAR_LEFT_SIDE :: 0 SCROLLBAR_RIGHT_SIDE :: 1 //---------------------------------------------------------------------------------- @@ -236,8 +236,8 @@ foreign lib { // Style set/get functions - GuiSetStyle :: proc(control: c.int, property: c.int, value: c.int) --- // Set one style property - GuiGetStyle :: proc(control: c.int, property: c.int) -> c.int --- // Get one style property + GuiSetStyle :: proc(control: GuiControl, property: GuiStyleProp, value: c.int) --- // Set one style property + GuiGetStyle :: proc(control: GuiControl, property: GuiStyleProp) -> c.int --- // Get one style property // Styles loading functions @@ -252,11 +252,11 @@ foreign lib { // Icons functionality - GuiIconText :: proc(iconId: c.int, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) + GuiIconText :: proc(iconId: GuiIconName, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) GuiSetIconScale :: proc(scale: c.int) --- // Set default icon drawing size GuiGetIcons :: proc() -> [^]u32 --- // Get raygui icons data pointer GuiLoadIcons :: proc(fileName: cstring, loadIconsName: bool) -> [^]cstring --- // Load raygui icons file (.rgi) into internal icons data - GuiDrawIcon :: proc(iconId: c.int, posX: c.int, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position + GuiDrawIcon :: proc(iconId: GuiIconName, posX, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position // Controls @@ -274,11 +274,11 @@ foreign lib { GuiLabel :: proc(bounds: Rectangle, text: cstring) -> c.int --- // Label control, shows text GuiButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Button control, returns true when clicked - GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked + GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked GuiToggle :: proc(bounds: Rectangle, text: cstring, active: ^bool) -> c.int --- // Toggle Button control, returns true when active GuiToggleGroup :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Toggle Group control, returns active toggle index GuiToggleSlider :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- - GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active + GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active GuiComboBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Combo Box control, returns selected item index GuiDropdownBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int, editMode: bool) -> bool --- // Dropdown Box control, returns selected item From 4f5b2bd12736b32c0a35496e8ca8f03d305c54b4 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:01:50 +0200 Subject: [PATCH 128/151] fix crash when you have 2 `#load_directory` calls with the same path --- src/check_builtin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index aa3be0bbd..15c63905e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1345,6 +1345,8 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c map_set(&c->info->load_directory_map, call, new_cache); } else { cache->file_error = file_error; + + map_set(&c->info->load_directory_map, call, cache); } }); From b47a15733df5d0cb858a2be0d4eb334df3d50536 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:06:13 +0200 Subject: [PATCH 129/151] implement `#exists(path)` --- src/check_builtin.cpp | 94 ++++++++++++++++++++++++++++++++----------- src/check_expr.cpp | 1 + src/checker.hpp | 9 +++++ 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 15c63905e..7e3bcb7ee 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } -gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_) { +gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) { ast_node(ce, CallExpr, call); ast_node(bd, BasicDirective, ce->proc); String builtin_name = bd->name.string; @@ -1105,12 +1105,16 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String gbFileError file_error = gbFileError_None; String data = {}; + bool exists = false; + LoadFileTier cache_tier = LoadFileTier_Invalid; LoadFileCache **cache_ptr = string_map_get(&c->info->load_file_cache, path); LoadFileCache *cache = cache_ptr ? *cache_ptr : nullptr; if (cache) { file_error = cache->file_error; data = cache->data; + exists = cache->exists; + cache_tier = cache->tier; } defer ({ if (cache == nullptr) { @@ -1118,60 +1122,78 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String new_cache->path = path; new_cache->data = data; new_cache->file_error = file_error; + new_cache->exists = exists; + new_cache->tier = cache_tier; string_map_init(&new_cache->hashes, 32); string_map_set(&c->info->load_file_cache, path, new_cache); if (cache_) *cache_ = new_cache; } else { cache->data = data; cache->file_error = file_error; + cache->exists = exists; + cache->tier = cache_tier; if (cache_) *cache_ = cache; } }); - TEMPORARY_ALLOCATOR_GUARD(); - char *c_str = alloc_cstring(temporary_allocator(), path); + if (tier > cache_tier) { + cache_tier = tier; - gbFile f = {}; - if (cache == nullptr) { + TEMPORARY_ALLOCATOR_GUARD(); + char *c_str = alloc_cstring(temporary_allocator(), path); + + gbFile f = {}; file_error = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + if (file_error == gbFileError_None) { + exists = true; + + switch(tier) { + case LoadFileTier_Exists: + // Nothing to do. + break; + case LoadFileTier_Contents: { + isize file_size = cast(isize)gb_file_size(&f); + if (file_size > 0) { + u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); + gb_file_read_at(&f, ptr, file_size, 0); + ptr[file_size] = '\0'; + data.text = ptr; + data.len = file_size; + } + break; + } + default: + GB_PANIC("Unhandled LoadFileTier"); + }; + } } - defer (gb_file_close(&f)); switch (file_error) { default: case gbFileError_Invalid: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; invalid file or cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; invalid file or cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_NotExists: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_Permission: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file permissions problem", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file permissions problem", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_None: // Okay break; - } - - if (cache == nullptr) { - isize file_size = cast(isize)gb_file_size(&f); - if (file_size > 0) { - u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); - gb_file_read_at(&f, ptr, file_size, 0); - ptr[file_size] = '\0'; - data.text = ptr; - data.len = file_size; - } - } + }; return true; } @@ -1263,7 +1285,7 @@ gb_internal LoadDirectiveResult check_load_directive(CheckerContext *c, Operand operand->mode = Addressing_Constant; LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache, LoadFileTier_Contents)) { operand->value = exact_value_string(cache->data); return LoadDirective_Success; } @@ -1391,7 +1413,7 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c for (FileInfo fi : list) { LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) { array_add(&file_caches, cache); } else { result = LoadDirective_Error; @@ -1490,6 +1512,30 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_source_code_location; operand->mode = Addressing_Value; + } else if (name == "exists") { + if (ce->args.count != 1) { + error(ce->close, "'#exists' expects 1 argument, got %td", ce->args.count); + return false; + } + + Operand o = {}; + check_expr(c, &o, ce->args[0]); + if (o.mode != Addressing_Constant || !is_type_string(o.type)) { + error(ce->args[0], "'#exists' expected a constant string argument"); + return false; + } + + operand->type = t_untyped_bool; + operand->mode = Addressing_Constant; + + String original_string = o.value.value_string; + LoadFileCache *cache = nullptr; + if (cache_load_file_directive(c, call, original_string, /* err_on_not_found=*/ false, &cache, LoadFileTier_Exists)) { + operand->value = exact_value_bool(cache->exists); + } else { + operand->value = exact_value_bool(false); + } + } else if (name == "load") { return check_load_directive(c, operand, call, type_hint, true) == LoadDirective_Success; } else if (name == "load_directory") { @@ -1542,7 +1588,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o String hash_kind = o_hash.value.value_string; LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, original_string, true, &cache)) { + if (cache_load_file_directive(c, call, original_string, true, &cache, LoadFileTier_Contents)) { MUTEX_GUARD(&c->info->load_file_mutex); // TODO(bill): make these procedures fast :P u64 hash_value = 0; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2e008fe93..3bf2af24a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7408,6 +7408,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c String name = bd->name.string; if ( name == "location" || + name == "exists" || name == "assert" || name == "panic" || name == "defined" || diff --git a/src/checker.hpp b/src/checker.hpp index 539b72b2d..e793540e3 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -336,7 +336,16 @@ struct ObjcMsgData { ObjcMsgKind kind; Type *proc_type; }; + +enum LoadFileTier { + LoadFileTier_Invalid, + LoadFileTier_Exists, + LoadFileTier_Contents, +}; + struct LoadFileCache { + LoadFileTier tier; + bool exists; String path; gbFileError file_error; String data; From d33668fa91b0f64cb6f69f315a2b2e8609fdbee0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:24:46 -0400 Subject: [PATCH 130/151] Fix partial parsing of "infinity" in `parse_f64_prefix` It was previously reporting an invalid number of characters parsed for any string other than "inf", "+inf", or "-inf". --- core/strconv/strconv.odin | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..990b2be2f 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -878,13 +878,10 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { s = s[1:] fallthrough case 'i', 'I': - n = common_prefix_len_ignore_case(s, "infinity") - if 3 < n && n < 8 { // "inf" or "infinity" - n = 3 - } - if n == 3 || n == 8 { + m := common_prefix_len_ignore_case(s, "infinity") + if m == 3 || m == 8 { // "inf" or "infinity" f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000 - n = nsign + 3 + n = nsign + m ok = true return } From 3e159736cdf7654d5739a1340458aa23aaccbf7a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:32:23 +0200 Subject: [PATCH 131/151] use `#exists` to provide good errors for common missing libraries --- src/check_expr.cpp | 1 + vendor/cgltf/cgltf.odin | 22 ++++++++++++++++++---- vendor/miniaudio/common.odin | 12 ++++++++---- vendor/miniaudio/data_conversion.odin | 6 +----- vendor/miniaudio/decoding.odin | 6 +----- vendor/miniaudio/device_io_procs.odin | 6 +----- vendor/miniaudio/effects.odin | 6 +----- vendor/miniaudio/encoding.odin | 6 +----- vendor/miniaudio/engine.odin | 6 +----- vendor/miniaudio/filtering.odin | 6 +----- vendor/miniaudio/generation.odin | 6 +----- vendor/miniaudio/job_queue.odin | 6 +----- vendor/miniaudio/logging.odin | 6 +----- vendor/miniaudio/node_graph.odin | 6 +----- vendor/miniaudio/resource_manager.odin | 6 +----- vendor/miniaudio/synchronization.odin | 6 +----- vendor/miniaudio/utilities.odin | 6 +----- vendor/miniaudio/vfs.odin | 6 +----- vendor/stb/image/stb_image.odin | 25 +++++++++++++++++++------ vendor/stb/image/stb_image_resize.odin | 21 +++++++++++++++++---- vendor/stb/image/stb_image_write.odin | 21 +++++++++++++++++---- vendor/stb/rect_pack/stb_rect_pack.odin | 21 +++++++++++++++++---- vendor/stb/truetype/stb_truetype.odin | 20 ++++++++++++++++---- vendor/stb/vorbis/stb_vorbis.odin | 20 +++++++++++++++----- 24 files changed, 143 insertions(+), 110 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3bf2af24a..c03fcb184 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8330,6 +8330,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A name == "assert" || name == "defined" || name == "config" || + name == "exists" || name == "load" || name == "load_hash" || name == "load_directory" || diff --git a/vendor/cgltf/cgltf.odin b/vendor/cgltf/cgltf.odin index 024e8dfaa..a5d474a7b 100644 --- a/vendor/cgltf/cgltf.odin +++ b/vendor/cgltf/cgltf.odin @@ -1,9 +1,23 @@ package cgltf -when ODIN_OS == .Windows { foreign import lib "lib/cgltf.lib" } -else when ODIN_OS == .Linux { foreign import lib "lib/cgltf.a" } -else when ODIN_OS == .Darwin { foreign import lib "lib/darwin/cgltf.a" } -else { foreign import lib "system:cgltf" } +@(private) +LIB :: ( + "lib/cgltf.lib" when ODIN_OS == .Windows + else "lib/cgltf.a" when ODIN_OS == .Linux + else "lib/darwin/cgltf.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled cgltf library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/cgltf/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:cgltf" +} import "core:c" diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index b38599d96..d72c3f251 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -8,12 +8,16 @@ when MINIAUDIO_SHARED { #panic("Shared linking for miniaudio is not supported yet") } -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" +@(private) +LIB :: "lib/miniaudio.lib" when ODIN_OS == .Windows else "lib/miniaudio.a" + +when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled miniaudio library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/miniaudio/src\"`") } +foreign import lib { LIB } + BINDINGS_VERSION_MAJOR :: 0 BINDINGS_VERSION_MINOR :: 11 BINDINGS_VERSION_REVISION :: 21 diff --git a/vendor/miniaudio/data_conversion.odin b/vendor/miniaudio/data_conversion.odin index aee26bc8c..c33f54707 100644 --- a/vendor/miniaudio/data_conversion.odin +++ b/vendor/miniaudio/data_conversion.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index 4433aa5a7..4860680c9 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 0d572ae2c..21ac1afd7 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } import "core:c" diff --git a/vendor/miniaudio/effects.odin b/vendor/miniaudio/effects.odin index 273845001..a3710ad88 100644 --- a/vendor/miniaudio/effects.odin +++ b/vendor/miniaudio/effects.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Delay diff --git a/vendor/miniaudio/encoding.odin b/vendor/miniaudio/encoding.odin index 63aa45c6d..da8389b06 100644 --- a/vendor/miniaudio/encoding.odin +++ b/vendor/miniaudio/encoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index 6eabd75c2..ecd3fb39d 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/filtering.odin b/vendor/miniaudio/filtering.odin index 31ddbd7a4..d1c053d20 100644 --- a/vendor/miniaudio/filtering.odin +++ b/vendor/miniaudio/filtering.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************** diff --git a/vendor/miniaudio/generation.odin b/vendor/miniaudio/generation.odin index 69be85234..746efcca7 100644 --- a/vendor/miniaudio/generation.odin +++ b/vendor/miniaudio/generation.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } waveform_type :: enum c.int { sine, diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin index baa71c5f1..01ee31216 100644 --- a/vendor/miniaudio/job_queue.odin +++ b/vendor/miniaudio/job_queue.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Slot Allocator diff --git a/vendor/miniaudio/logging.odin b/vendor/miniaudio/logging.odin index 52b1c7980..afddf8e68 100644 --- a/vendor/miniaudio/logging.odin +++ b/vendor/miniaudio/logging.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c/libc" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } MAX_LOG_CALLBACKS :: 4 diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 09ab50a3b..63482413b 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index f27f3a53a..0284db86b 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin index cd4b0a5f0..012f52c2c 100644 --- a/vendor/miniaudio/synchronization.odin +++ b/vendor/miniaudio/synchronization.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index d518a514a..8728f40dc 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index 475d118fc..b045a1501 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index c7caf801e..828a1c2bd 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -2,13 +2,26 @@ package stb_image import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_image.lib" when ODIN_OS == .Windows + else "../lib/stb_image.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbi { LIB } +} else { + foreign import stbi "system:stb_image" +} + #assert(size_of(c.int) == size_of(b32)) - - when ODIN_OS == .Windows { foreign import stbi "../lib/stb_image.lib" } -else when ODIN_OS == .Linux { foreign import stbi "../lib/stb_image.a" } -else when ODIN_OS == .Darwin { foreign import stbi "../lib/darwin/stb_image.a" } -else { foreign import stbi "system:stb_image" } - #assert(size_of(b32) == size_of(c.int)) // diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index c464964df..d407a1852 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import lib "../lib/stb_image_resize.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_image_resize.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_image_resize.a" } -else { foreign import lib "system:stb_image_resize" } +@(private) +RESIZE_LIB :: ( + "../lib/stb_image_resize.lib" when ODIN_OS == .Windows + else "../lib/stb_image_resize.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_resize.a" when ODIN_OS == .Darwin + else "" +) +when RESIZE_LIB != "" { + when !#exists(RESIZE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { RESIZE_LIB } +} else { + foreign import lib "system:stb_image_resize" +} ////////////////////////////////////////////////////////////////////////////// // diff --git a/vendor/stb/image/stb_image_write.odin b/vendor/stb/image/stb_image_write.odin index 9ed97eb48..f030f1e28 100644 --- a/vendor/stb/image/stb_image_write.odin +++ b/vendor/stb/image/stb_image_write.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import stbiw "../lib/stb_image_write.lib" } -else when ODIN_OS == .Linux { foreign import stbiw "../lib/stb_image_write.a" } -else when ODIN_OS == .Darwin { foreign import stbiw "../lib/darwin/stb_image_write.a" } -else { foreign import stbiw "system:stb_image_write" } +@(private) +WRITE_LIB :: ( + "../lib/stb_image_write.lib" when ODIN_OS == .Windows + else "../lib/stb_image_write.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_write.a" when ODIN_OS == .Darwin + else "" +) +when WRITE_LIB != "" { + when !#exists(WRITE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbiw { WRITE_LIB } +} else { + foreign import stbiw "system:stb_image_write" +} write_func :: proc "c" (ctx: rawptr, data: rawptr, size: c.int) diff --git a/vendor/stb/rect_pack/stb_rect_pack.odin b/vendor/stb/rect_pack/stb_rect_pack.odin index 3a2544b81..6c0b56378 100644 --- a/vendor/stb/rect_pack/stb_rect_pack.odin +++ b/vendor/stb/rect_pack/stb_rect_pack.odin @@ -4,10 +4,23 @@ import "core:c" #assert(size_of(b32) == size_of(c.int)) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_rect_pack.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_rect_pack.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_rect_pack.a" } -else { foreign import lib "system:stb_rect_pack" } +@(private) +LIB :: ( + "../lib/stb_rect_pack.lib" when ODIN_OS == .Windows + else "../lib/stb_rect_pack.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_rect_pack.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:stb_rect_pack" +} Coord :: distinct c.int _MAXVAL :: max(Coord) diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 1600041de..876138c3a 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -3,11 +3,23 @@ package stb_truetype import c "core:c" import stbrp "vendor:stb/rect_pack" - when ODIN_OS == .Windows { foreign import stbtt "../lib/stb_truetype.lib" } -else when ODIN_OS == .Linux { foreign import stbtt "../lib/stb_truetype.a" } -else when ODIN_OS == .Darwin { foreign import stbtt "../lib/darwin/stb_truetype.a" } -else { foreign import stbtt "system:stb_truetype" } +@(private) +LIB :: ( + "../lib/stb_truetype.lib" when ODIN_OS == .Windows + else "../lib/stb_truetype.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_truetype.a" when ODIN_OS == .Darwin + else "" +) +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbtt { LIB } +} else { + foreign import stbtt "system:stb_truetype" +} /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// diff --git a/vendor/stb/vorbis/stb_vorbis.odin b/vendor/stb/vorbis/stb_vorbis.odin index 0c887a473..867ffb86d 100644 --- a/vendor/stb/vorbis/stb_vorbis.odin +++ b/vendor/stb/vorbis/stb_vorbis.odin @@ -2,13 +2,23 @@ package stb_vorbis import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_vorbis.lib" when ODIN_OS == .Windows + else "../lib/stb_vorbis.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_vorbis.a" when ODIN_OS == .Darwin + else "" +) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_vorbis.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_vorbis.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_vorbis.a" } -else { foreign import lib "system:stb_vorbis" } - +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + foreign import lib { LIB } +} else { + foreign import lib "system:stb_vorbis" +} /////////// THREAD SAFETY From 2a526058b3cbcd645dd35f3541ba1fc305dd02f9 Mon Sep 17 00:00:00 2001 From: laytan Date: Tue, 4 Jun 2024 20:15:47 +0200 Subject: [PATCH 132/151] fix passing pointer to constant in non-odin cc --- src/llvm_backend_proc.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3b9b1be05..6cb1efab2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1097,15 +1097,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c ptr = lb_address_from_load_or_generate_local(p, x); } } else { - if (LLVMIsConstant(x.value)) { - // NOTE(bill): if the value is already constant, then just it as a global variable - // and pass it by pointer - lbAddr addr = lb_add_global_generated(p->module, original_type, x); - lb_make_global_private_const(addr); - ptr = addr.addr; - } else { - ptr = lb_copy_value_to_ptr(p, x, original_type, 16); - } + ptr = lb_copy_value_to_ptr(p, x, original_type, 16); } array_add(&processed_args, ptr); } From 7d670f65624c7c1f0d9a93808a99e259b95bd4b6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:39:31 -0400 Subject: [PATCH 133/151] Add initial test suite for `core:strconv` --- tests/core/Makefile | 4 ++ tests/core/build.bat | 5 ++ tests/core/strconv/test_core_strconv.odin | 79 +++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tests/core/strconv/test_core_strconv.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index 85f3783b4..79af9c3c7 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -25,6 +25,7 @@ all_bsd: download_test_assets \ reflect_test \ runtime_test \ slice_test \ + strconv_test \ strings_test \ thread_test \ time_test @@ -98,6 +99,9 @@ runtime_test: slice_test: $(ODIN) test slice $(COMMON) -out:test_core_slice +strconv_test: + $(ODIN) test strconv $(COMMON) -out:test_core_strconv + strings_test: $(ODIN) test strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index 67ac10f86..18506408a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -103,6 +103,11 @@ echo Running core:slice tests echo --- %PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b +echo --- +echo Running core:strconv tests +echo --- +%PATH_TO_ODIN% test strconv %COMMON% -out:test_core_strconv.exe || exit /b + echo --- echo Running core:strings tests echo --- diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin new file mode 100644 index 000000000..9d713a356 --- /dev/null +++ b/tests/core/strconv/test_core_strconv.odin @@ -0,0 +1,79 @@ +package test_core_strconv + +import "core:math" +import "core:strconv" +import "core:testing" + +@(test) +test_infinity :: proc(t: ^testing.T) { + pos_inf := math.inf_f64(+1) + neg_inf := math.inf_f64(-1) + + n: int + s := "infinity" + + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 3 { // "inf" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + } else if i == 8 { // "infinity" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + s = "+infinity" + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 4 { // "+inf" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 4) + testing.expect_value(t, ok, true) + } else if i == 9 { // "+infinity" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 9) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + s = "-infinity" + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 4 { // "-inf" + testing.expect_value(t, f, neg_inf) + testing.expect_value(t, n, 4) + testing.expect_value(t, ok, true) + } else if i == 9 { // "-infinity" + testing.expect_value(t, f, neg_inf) + testing.expect_value(t, n, 9) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + // Make sure odd casing works. + batch := [?]string {"INFiniTY", "iNfInItY", "InFiNiTy"} + for ss in batch { + f, ok := strconv.parse_f64(ss, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, true) + } +} From cd99625dd352e2ceaf621c8ae0f50de8ace3177d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 20:19:34 +0200 Subject: [PATCH 134/151] ci: compile needed libraries --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb2077d1..c808bb05e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 @@ -109,6 +115,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 @@ -148,6 +160,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 From c656a9e4cdd566b1aa473227d9d9a35b3c6812d3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:08:19 -0400 Subject: [PATCH 135/151] Fix and subsume `test_issue_2087` into `strconv` test suite The full "infinity" strings were expected to be partial consumes, but this is not the case. That has been fixed and the relevant extra tests from that file have been added to this test suite. Fixes #2670 --- tests/core/strconv/test_core_strconv.odin | 60 ++++++++++++++++++++++ tests/issues/test_issue_2087.odin | 62 ----------------------- 2 files changed, 60 insertions(+), 62 deletions(-) delete mode 100644 tests/issues/test_issue_2087.odin diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index 9d713a356..d33ac06b6 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -4,6 +4,56 @@ import "core:math" import "core:strconv" import "core:testing" +@(test) +test_float :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("1.2", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("1.2a", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("+", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("-", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + +} + +@(test) +test_nan :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("nan", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("nAN", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("Nani", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) +} + @(test) test_infinity :: proc(t: ^testing.T) { pos_inf := math.inf_f64(+1) @@ -19,14 +69,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 3) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else if i == 8 { // "infinity" testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 8) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -38,14 +91,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 4) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else if i == 9 { // "+infinity" testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 9) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -57,14 +113,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, neg_inf) testing.expect_value(t, n, 4) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else if i == 9 { // "-infinity" testing.expect_value(t, f, neg_inf) testing.expect_value(t, n, 9) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -75,5 +134,6 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 8) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } } diff --git a/tests/issues/test_issue_2087.odin b/tests/issues/test_issue_2087.odin deleted file mode 100644 index 26b6d487d..000000000 --- a/tests/issues/test_issue_2087.odin +++ /dev/null @@ -1,62 +0,0 @@ -// Tests issue #2087 https://github.com/odin-lang/Odin/issues/2087 -package test_issues - -import "core:math" -import "core:strconv" -import "core:testing" - -@(test) -test_parse_float :: proc(t: ^testing.T) { - { - f, ok := strconv.parse_f64("1.2") - testing.expect(t, ok && f == 1.2, "expected f64(1.2), fully consumed") - f, ok = strconv.parse_f64("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f64(1.2), partially consumed") - f, ok = strconv.parse_f64("+") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - f, ok = strconv.parse_f64("-") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - - - f, ok = strconv.parse_f64("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), fully consumed") - f, ok = strconv.parse_f64("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), partially consumed") - f, ok = strconv.parse_f64("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - f, ok = strconv.parse_f64("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - } - { - f, ok := strconv.parse_f32("1.2") - testing.expect(t, ok && f == 1.2, "expected f32(1.2), fully consumed") - - f, ok = strconv.parse_f32("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f32(1.2), partially consumed") - - f, ok = strconv.parse_f32("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), fully consumed") - f, ok = strconv.parse_f32("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), partially consumed") - f, ok = strconv.parse_f32("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - f, ok = strconv.parse_f32("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - } -} \ No newline at end of file From dbaf1a1ce0a4228e9b3b07a020069dbf0b17136c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 20:26:21 +0200 Subject: [PATCH 136/151] compile stb_vorbis --- vendor/stb/src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/src/Makefile b/vendor/stb/src/Makefile index a6db3e297..1027cf8d2 100644 --- a/vendor/stb/src/Makefile +++ b/vendor/stb/src/Makefile @@ -14,7 +14,7 @@ unix: $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o $(AR) rcs ../lib/stb_truetype.a stb_truetype.o $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o - #$(AR) rcs ../lib/stb_vorbis_pack.a stb_vorbis_pack.o + $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o From c2ddf4266f478a5d331a7301d26cef4432b21455 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 22:26:51 +0200 Subject: [PATCH 137/151] Update `delete_old_binaries.py` --- ci/delete_old_binaries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 6ea3cb78d..f7d5d02af 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -21,8 +21,8 @@ def main(): delta = now - date if delta.days > days_to_keep: - print(f'Deleting {parts[5]}') - execute_cli(f'b2 rm {parts[0]}') + print(f'Deleting b2://{bucket}/{parts[5]}') + execute_cli(f'b2 rm b2://{bucket}/{parts[5]}') def execute_cli(command): From 9d234998c0e4e412155c7f1debf32b562fa59dad Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 22:44:49 +0200 Subject: [PATCH 138/151] Update `create_nightly_json` --- ci/create_nightly_json.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index 03c653040..3a1fcd1ef 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -13,7 +13,8 @@ def main(): for x in files_lines: parts = x.split(" ", 1) if parts[0]: - json_str = execute_cli(f"b2 file info {parts[0]}") + print(f"Parts[0]: {parts[0]}", flush=True) + json_str = execute_cli(f"b2 file info b2://{bucket}/{parts[0]}") data = json.loads(json_str) name = remove_prefix(data['fileName'], "nightly/") url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" From 265e6aa781e487a3880f97bb6e8c12d2abcb8b57 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:59:07 -0400 Subject: [PATCH 139/151] Add `parse_complex/quaternion*` to `core:strconv` --- core/strconv/strconv.odin | 269 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..2c8365922 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1124,6 +1124,275 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { ok = !overflow return } +/* +Parses a 128-bit complex number from a string + +**Inputs** +- str: The input string containing a 128-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex128_example :: proc() { + n: int + c, ok := strconv.parse_complex128("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex128("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 128-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok: bool) { + real_value, imag_value: f64 + nr_r, nr_i: int + + real_value, nr_r, _ = parse_f64_prefix(str) + imag_value, nr_i, _ = parse_f64_prefix(str[nr_r:]) + + i_parsed := len(str) >= nr_r + nr_i + 1 && str[nr_r + nr_i] == 'i' + if !i_parsed { + // No `i` means we refuse to treat the second float we parsed as an + // imaginary value. + imag_value = 0 + nr_i = 0 + } + + ok = i_parsed && len(str) == nr_r + nr_i + 1 + + if n != nil { + n^ = nr_r + nr_i + (1 if i_parsed else 0) + } + + value = complex(real_value, imag_value) + return +} +/* +Parses a 64-bit complex number from a string + +**Inputs** +- str: The input string containing a 64-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex64_example :: proc() { + n: int + c, ok := strconv.parse_complex64("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex64("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 64-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex64)v, ok +} +/* +Parses a 32-bit complex number from a string + +**Inputs** +- str: The input string containing a 32-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex32_example :: proc() { + n: int + c, ok := strconv.parse_complex32("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex32("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 32-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex32)v, ok +} +/* +Parses a 256-bit quaternion from a string + +**Inputs** +- str: The input string containing a 256-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion256_example :: proc() { + n: int + q, ok := strconv.parse_quaternion256("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 256-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256, ok: bool) { + iterate_and_assign :: proc (iter: ^string, terminator: byte, nr_total: ^int, state: bool) -> (value: f64, ok: bool) { + if !state { + return + } + + nr: int + value, nr, _ = parse_f64_prefix(iter^) + iter^ = iter[nr:] + + if len(iter) > 0 && iter[0] == terminator { + iter^ = iter[1:] + nr_total^ += nr + 1 + ok = true + } else { + value = 0 + } + + return + } + + real_value, imag_value, jmag_value, kmag_value: f64 + nr: int + + real_value, nr, _ = parse_f64_prefix(str) + iter := str[nr:] + + // Need to have parsed at least something in order to get started. + ok = nr > 0 + + // Quaternion parsing is done this way to honour the rest of the API with + // regards to partial parsing. Otherwise, we could error out early. + imag_value, ok = iterate_and_assign(&iter, 'i', &nr, ok) + jmag_value, ok = iterate_and_assign(&iter, 'j', &nr, ok) + kmag_value, ok = iterate_and_assign(&iter, 'k', &nr, ok) + + if len(iter) != 0 { + ok = false + } + + if n != nil { + n^ = nr + } + + value = quaternion( + real = real_value, + imag = imag_value, + jmag = jmag_value, + kmag = kmag_value) + return +} +/* +Parses a 128-bit quaternion from a string + +**Inputs** +- str: The input string containing a 128-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion128_example :: proc() { + n: int + q, ok := strconv.parse_quaternion128("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 128-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion128)v, ok +} +/* +Parses a 64-bit quaternion from a string + +**Inputs** +- str: The input string containing a 64-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion64_example :: proc() { + n: int + q, ok := strconv.parse_quaternion64("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 64-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion64)v, ok +} /* Appends a boolean value as a string to the given buffer From b4cfae222cf51ff39d62d38ac3350192cc43b174 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:47:19 -0400 Subject: [PATCH 140/151] Strike incorrect note from `parse_f64_prefix` doc --- core/strconv/strconv.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..d4727d862 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -835,17 +835,21 @@ Example: n, _, ok = strconv.parse_f64_prefix("12.34e2") fmt.printfln("%.3f %v", n, ok) + + n, _, ok = strconv.parse_f64_prefix("13.37 hellope") + fmt.printfln("%.3f %v", n, ok) } Output: 0.000 false 1234.000 true + 13.370 true **Returns** - value: The parsed 64-bit floating point number. - nr: The length of the parsed substring. -- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. +- ok: `false` if a base 10 float could not be found */ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int { From 1fc6ff91b205836fad0fb30b36bfd042e381aa19 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:17:57 -0400 Subject: [PATCH 141/151] Add test for `infinity` with trailing characters --- tests/core/strconv/test_core_strconv.odin | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index d33ac06b6..ed4adaf01 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -136,4 +136,19 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, ok, true) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } + + // Explicitly check how trailing characters are handled. + s = "infinityyyy" + f, ok := strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + + s = "inflippity" + f, ok = strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } From 25feff3eb4c3845e6da4e8e56010301e316b2dd9 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:50:08 -0400 Subject: [PATCH 142/151] Permit parsing of incomplete `infinity` but do not return true To clarify, `parse_f64` will indeed take `infi` to mean `+Inf` and return that as the value, but it will not return `ok = true`. It treats it as `inf` followed by any other trailing character. `parse_f64_prefix` is the lenient one which will return true so long as it finds some meaningful value. --- core/strconv/strconv.odin | 10 ++++- tests/core/strconv/test_core_strconv.odin | 45 +++++++++-------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 990b2be2f..60b4b4e2e 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -879,9 +879,15 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { fallthrough case 'i', 'I': m := common_prefix_len_ignore_case(s, "infinity") - if m == 3 || m == 8 { // "inf" or "infinity" + if 3 <= m && m < 9 { // "inf" to "infinity" f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000 - n = nsign + m + if m == 8 { + // We only count the entire prefix if it is precisely "infinity". + n = nsign + m + } else { + // The string was either only "inf" or incomplete. + n = nsign + 3 + } ok = true return } diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index ed4adaf01..6b70654cc 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -62,18 +62,15 @@ test_infinity :: proc(t: ^testing.T) { n: int s := "infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 3 { // "inf" + if i >= 3 { // "inf" .. "infinity" + expected_n := 8 if i == 8 else 3 + expected_ok := i == 3 || i == 8 testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 3) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Inf) - } else if i == 8 { // "infinity" - testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 8) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) @@ -84,18 +81,15 @@ test_infinity :: proc(t: ^testing.T) { } s = "+infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 4 { // "+inf" + if i >= 4 { // "+inf" .. "+infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 4) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Inf) - } else if i == 9 { // "+infinity" - testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 9) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) @@ -106,18 +100,15 @@ test_infinity :: proc(t: ^testing.T) { } s = "-infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 4 { // "-inf" + if i >= 4 { // "-inf" .. "infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 testing.expect_value(t, f, neg_inf) - testing.expect_value(t, n, 4) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) - } else if i == 9 { // "-infinity" - testing.expect_value(t, f, neg_inf) - testing.expect_value(t, n, 9) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else { // invalid substring testing.expect_value(t, f, 0) From b2f9f0af689ff6ffdd9db149c04a99cd6b2a46b4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:01:30 -0400 Subject: [PATCH 143/151] Fix some typos --- base/runtime/docs.odin | 2 +- core/odin/ast/ast.odin | 2 +- core/time/{iso8061.odin => iso8601.odin} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename core/time/{iso8061.odin => iso8601.odin} (100%) diff --git a/base/runtime/docs.odin b/base/runtime/docs.odin index 865eeb9ef..f6b439aa0 100644 --- a/base/runtime/docs.odin +++ b/base/runtime/docs.odin @@ -157,7 +157,7 @@ __dynamic_map_get // dynamic map calls __dynamic_map_set // dynamic map calls -## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) +## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals) __dynamic_array_reserve __dynamic_array_append diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 7891fb12d..229f03d3d 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -753,7 +753,7 @@ Array_Type :: struct { using node: Expr, open: tokenizer.Pos, tag: ^Expr, - len: ^Expr, // Ellipsis node for [?]T arrray types, nil for slice types + len: ^Expr, // Ellipsis node for [?]T array types, nil for slice types close: tokenizer.Pos, elem: ^Expr, } diff --git a/core/time/iso8061.odin b/core/time/iso8601.odin similarity index 100% rename from core/time/iso8061.odin rename to core/time/iso8601.odin From 58f07698e849ca7542c53f9687b6c3d4a7f478e9 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 10:18:47 +0200 Subject: [PATCH 144/151] Added arm64 support for NetBSD --- .github/workflows/ci.yml | 3 ++- src/build_settings.cpp | 17 +++++++++++++++-- src/gb/gb.h | 3 +++ src/threading.cpp | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aefc8add5..33bc303f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build, Check, and Test - timeout-minutes: 25 + timeout-minutes: 15 uses: vmactions/netbsd-vm@v1 with: release: "10.0" @@ -31,6 +31,7 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 + ./odin check examples/all -vet -strict-style -target:netbsd_arm64 (cd tests/core; gmake all_bsd) (cd tests/internal; gmake all_bsd) (cd tests/issues; ./run.sh) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3c7ff3f1e..a7e455818 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1039,6 +1039,13 @@ gb_global TargetMetrics target_netbsd_amd64 = { str_lit("x86_64-unknown-netbsd-elf"), }; +gb_global TargetMetrics target_netbsd_arm64 = { + TargetOs_netbsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-netbsd-elf"), +}; + gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, @@ -1154,8 +1161,10 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, - { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, + { str_lit("netbsd_arm64"), &target_netbsd_arm64 }, + + { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, @@ -1916,7 +1925,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; #elif defined(GB_SYSTEM_NETBSD) - metrics = &target_netbsd_amd64; + #if defined(GB_CPU_ARM) + metrics = &target_netbsd_arm64; + #else + metrics = &target_netbsd_amd64; + #endif #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) diff --git a/src/gb/gb.h b/src/gb/gb.h index 17d5e97d1..22a30a04b 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -256,6 +256,7 @@ extern "C" { #if defined(GB_SYSTEM_NETBSD) #include + #include #define lseek64 lseek #endif @@ -3027,6 +3028,8 @@ gb_inline u32 gb_thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for gb_thread_current_id() #endif diff --git a/src/threading.cpp b/src/threading.cpp index 48c58e8f4..717dcb874 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -494,6 +494,8 @@ gb_internal u32 thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for thread_current_id() #endif From ed6667ebf2fd5d85bcafc789c8e5e5adbd7524cc Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 11:06:14 +0200 Subject: [PATCH 145/151] Propper thread identification on NetBSD --- core/os/os_netbsd.odin | 7 ++++++- core/sync/primitives_netbsd.odin | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index e8e551340..ab4f877bb 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -328,6 +328,11 @@ foreign dl { @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } +@(private) +foreign libc { + _lwp_self :: proc() -> i32 --- +} + // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. is_path_separator :: proc(r: rune) -> bool { @@ -721,7 +726,7 @@ exit :: proc "contextless" (code: int) -> ! { } current_thread_id :: proc "contextless" () -> int { - return cast(int) unix.pthread_self() + return int(_lwp_self()) } dlopen :: proc(filename: string, flags: int) -> rawptr { diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin index 042e744e8..594f2ff5c 100644 --- a/core/sync/primitives_netbsd.odin +++ b/core/sync/primitives_netbsd.odin @@ -1,8 +1,12 @@ //+private package sync -import "core:sys/unix" +foreign import libc "system:c" + +foreign libc { + _lwp_self :: proc "c" () -> i32 --- +} _current_thread_id :: proc "contextless" () -> int { - return cast(int) unix.pthread_self() + return int(_lwp_self()) } From 929437c7bc6c5e9efaff988930e2cdc7d07602df Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 11:17:13 +0200 Subject: [PATCH 146/151] Fixed broken import --- core/os/os_netbsd.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index ab4f877bb..c0f237bf5 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -5,7 +5,6 @@ foreign import libc "system:c" import "base:runtime" import "core:strings" -import "core:sys/unix" import "core:c" Handle :: distinct i32 From f0e98372fb361cfd63e390eb2593b1457bc2e9c6 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 15:37:02 +0200 Subject: [PATCH 147/151] Minor cleanup of makefiles and scripts --- .github/workflows/ci.yml | 1 - tests/internal/Makefile | 10 ++++++---- tests/issues/run.sh | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33bc303f0..2884fb301 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 - ln -s /usr/pkg/bin/bash /bin/bash run: | git config --global --add safe.directory $(pwd) gmake release diff --git a/tests/internal/Makefile b/tests/internal/Makefile index fb22767d2..307badb41 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,7 +1,9 @@ ODIN=../../odin COMMON=-file -vet -strict-style -o:minimal -all: asan_test rtti_test map_test pow_test 128_test string_compare_test +all: all_bsd asan_test + +all_bsd: rtti_test map_test pow_test 128_test string_compare_test rtti_test: $(ODIN) test test_rtti.odin $(COMMON) @@ -15,8 +17,8 @@ pow_test: 128_test: $(ODIN) test test_128.odin $(COMMON) +string_compare_test: + $(ODIN) test test_string_compare.odin $(COMMON) + asan_test: $(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug - -string_compare_test: - $(ODIN) test test_string_compare.odin $(COMMON) \ No newline at end of file diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 24b388b07..a5280485e 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu mkdir -p build From cd4375061ce005ae66826a3082c7780528455d5d Mon Sep 17 00:00:00 2001 From: Mark Sniffen Date: Wed, 5 Jun 2024 10:11:18 -0400 Subject: [PATCH 148/151] D3D11 - fixed VIDEO_SUPPORT flag --- vendor/directx/d3d11/d3d11.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/directx/d3d11/d3d11.odin b/vendor/directx/d3d11/d3d11.odin index a1e3cf039..3af0f2965 100644 --- a/vendor/directx/d3d11/d3d11.odin +++ b/vendor/directx/d3d11/d3d11.odin @@ -3374,7 +3374,7 @@ CREATE_DEVICE_FLAG :: enum u32 { DEBUGGABLE = 6, PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 7, DISABLE_GPU_TIMEOUT = 8, - VIDEO_SUPPORT = 12, + VIDEO_SUPPORT = 11, } PFN_CREATE_DEVICE :: #type proc "c" (a0: ^dxgi.IAdapter, a1: DRIVER_TYPE, a2: HMODULE, a3: u32, a4: ^FEATURE_LEVEL, a5: u32, a6: u32, a7: ^^IDevice, a8: ^FEATURE_LEVEL, a9: ^^IDeviceContext) -> HRESULT From cbabcb0907e6430571c14d21fcb09c150275d99b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 5 Jun 2024 15:43:15 +0100 Subject: [PATCH 149/151] Fix #3682 --- src/check_expr.cpp | 7 +++++++ src/llvm_backend_expr.cpp | 20 ++++++++++++++++++-- src/types.cpp | 9 +++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 0830f65bd..d2d01deda 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8866,6 +8866,10 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + is_constant = false; + ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type: %s", type_to_string(ft)); break; @@ -8892,6 +8896,9 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + nested_ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type %s", type_to_string(nested_ft)); break; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c12489598..7772ba930 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4533,10 +4533,26 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (lb_is_nested_possibly_constant(type, sel, elem)) { continue; } - lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); field_expr = lb_build_expr(p, elem); field_expr = lb_emit_conv(p, field_expr, sel.entity->type); - lb_emit_store(p, dst, field_expr); + if (sel.is_bit_field) { + Selection sub_sel = trim_selection(sel); + lbValue trimmed_dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sub_sel); + Type *bf = base_type(type_deref(trimmed_dst.type)); + if (is_type_pointer(bf)) { + trimmed_dst = lb_emit_load(p, trimmed_dst); + bf = base_type(type_deref(trimmed_dst.type)); + } + GB_ASSERT(bf->kind == Type_BitField); + + isize idx = sel.index[sel.index.count-1]; + lbAddr dst = lb_addr_bit_field(trimmed_dst, bf->BitField.fields[idx]->type, bf->BitField.bit_offsets[idx], bf->BitField.bit_sizes[idx]); + lb_addr_store(p, dst, field_expr); + + } else { + lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); + lb_emit_store(p, dst, field_expr); + } continue; } diff --git a/src/types.cpp b/src/types.cpp index e568d2af2..618e5bd8a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -457,6 +457,15 @@ gb_internal Selection sub_selection(Selection const &sel, isize offset) { return res; } +gb_internal Selection trim_selection(Selection const &sel) { + Selection res = {}; + res.index.data = sel.index.data; + res.index.count = gb_max(sel.index.count - 1, 0); + res.index.capacity = res.index.count; + return res; +} + + gb_global Type basic_types[] = { {Type_Basic, {Basic_Invalid, 0, 0, STR_LIT("invalid type")}}, From 460ffe1aee6688fa9887b861ad4ab9a2fd431598 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Jun 2024 21:04:35 +0200 Subject: [PATCH 150/151] Rewrite upload_b2 nightly action against B2 SDK --- .github/workflows/nightly.yml | 31 ++------ ci/create_nightly_json.py | 51 ------------- ci/delete_old_binaries.py | 33 -------- ci/nightly.py | 140 ++++++++++++++++++++++++++++++++++ ci/upload_create_nightly.sh | 25 ------ 5 files changed, 148 insertions(+), 132 deletions(-) delete mode 100644 ci/create_nightly_json.py delete mode 100644 ci/delete_old_binaries.py create mode 100644 ci/nightly.py delete mode 100755 ci/upload_create_nightly.sh diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 221ab1cdb..8d8392f95 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -151,11 +151,11 @@ jobs: with: python-version: '3.8.x' - - name: Install B2 CLI + - name: Install B2 SDK shell: bash run: | python -m pip install --upgrade pip - pip install --upgrade b2 + pip install --upgrade b2sdk - name: Display Python version run: python -c "import sys; print(sys.version)" @@ -188,24 +188,9 @@ jobs: BUCKET: ${{ secrets.B2_BUCKET }} DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | - echo Authorizing B2 account - b2 account authorize "$APPID" "$APPKEY" - - echo Uploading artifcates to B2 - chmod +x ./ci/upload_create_nightly.sh - ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/ - ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip - - echo Deleting old artifacts in B2 - python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP" - - echo Creating nightly.json - python3 ci/create_nightly_json.py "$BUCKET" > nightly.json - - echo Uploading nightly.json - b2 upload-file "$BUCKET" nightly.json nightly.json - - echo Clear B2 account info - b2 clear-account + python3 ci/nightly.py artifact windows-amd64 windows_artifacts/ + python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip + python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip + python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip + python3 ci/nightly.py prune + python3 ci/nightly.py json \ No newline at end of file diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py deleted file mode 100644 index 3a1fcd1ef..000000000 --- a/ci/create_nightly_json.py +++ /dev/null @@ -1,51 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - - files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n") - for x in files_lines: - parts = x.split(" ", 1) - if parts[0]: - print(f"Parts[0]: {parts[0]}", flush=True) - json_str = execute_cli(f"b2 file info b2://{bucket}/{parts[0]}") - data = json.loads(json_str) - name = remove_prefix(data['fileName'], "nightly/") - url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" - sha1 = data['contentSha1'] - size = int(data['size']) - ts = int(data['fileInfo']['src_last_modified_millis']) - date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d') - - if date not in files_by_date.keys(): - files_by_date[date] = [] - - files_by_date[date].append({ - 'name': name, - 'url': url, - 'sha1': sha1, - 'sizeInBytes': size, - }) - - now = datetime.datetime.utcnow().isoformat() - - print(json.dumps({ - 'last_updated' : now, - 'files': files_by_date - }, sort_keys=True, indent=4)) - -def remove_prefix(text, prefix): - return text[text.startswith(prefix) and len(prefix):] - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py deleted file mode 100644 index f7d5d02af..000000000 --- a/ci/delete_old_binaries.py +++ /dev/null @@ -1,33 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - days_to_keep = int(sys.argv[2]) - print(f"Looking for binaries to delete older than {days_to_keep} days") - - files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") - for x in files_lines: - parts = [y for y in x.split(' ') if y] - - if parts and parts[0]: - date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0) - now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - delta = now - date - - if delta.days > days_to_keep: - print(f'Deleting b2://{bucket}/{parts[5]}') - execute_cli(f'b2 rm b2://{bucket}/{parts[5]}') - - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file diff --git a/ci/nightly.py b/ci/nightly.py new file mode 100644 index 000000000..7a23fd2c2 --- /dev/null +++ b/ci/nightly.py @@ -0,0 +1,140 @@ +import os +import sys +from zipfile import ZipFile +from b2sdk.v2 import InMemoryAccountInfo, B2Api +from datetime import datetime +import json + +UPLOAD_FOLDER = "nightly/" + +info = InMemoryAccountInfo() +b2_api = B2Api(info) +application_key_id = os.environ['APPID'] +application_key = os.environ['APPKEY'] +bucket_name = os.environ['BUCKET'] +days_to_keep = os.environ['DAYS_TO_KEEP'] + +def auth() -> bool: + try: + realm = b2_api.account_info.get_realm() + return True # Already authenticated + except: + pass # Not yet authenticated + + err = b2_api.authorize_account("production", application_key_id, application_key) + return err == None + +def get_bucket(): + if not auth(): sys.exit(1) + return b2_api.get_bucket_by_name(bucket_name) + +def remove_prefix(text: str, prefix: str) -> str: + return text[text.startswith(prefix) and len(prefix):] + +def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d")) + + source_zip_name = artifact + if not artifact.endswith(".zip"): + print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") + + source_zip_name = destination_zip_name + with ZipFile(source_zip_name, 'w') as z: + for root, directory, filenames in os.walk(artifact): + for file in filenames: + file_path = os.path.join(root, file) + zip_path = os.path.join("dist", os.path.relpath(file_path, artifact)) + z.write(file_path, zip_path) + + if not os.path.exists(source_zip_name): + print(f"Error: Newly created ZIP archive {source_zip_name} not found.") + return 1 + + print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name)) + bucket = get_bucket() + res = bucket.upload_local_file( + source_zip_name, # Local file to upload + "nightly/" + destination_zip_name, # B2 destination path + ) + return 0 + +def prune_artifacts(): + print(f"Looking for binaries to delete older than {days_to_keep} days") + + bucket = get_bucket() + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0) + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + delta = now - date + + if delta.days > int(days_to_keep): + print("Deleting {}".format(file.file_name)) + file.delete() + + return 0 + +def update_nightly_json(): + print(f"Updating nightly.json with files {days_to_keep} days or newer") + + files_by_date = {} + + bucket = get_bucket() + + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d') + name = remove_prefix(file.file_name, UPLOAD_FOLDER) + sha1 = file.content_sha1 + size = file.size + url = bucket.get_download_url(file.file_name) + + if date not in files_by_date.keys(): + files_by_date[date] = [] + + files_by_date[date].append({ + 'name': name, + 'url': url, + 'sha1': sha1, + 'sizeInBytes': size, + }) + + now = datetime.utcnow().isoformat() + + nightly = json.dumps({ + 'last_updated' : now, + 'files': files_by_date + }, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8') + + res = bucket.upload_bytes( + nightly, # JSON bytes + "nightly.json", # B2 destination path + ) + return 0 + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("Usage: {} [arguments]".format(sys.argv[0])) + print("\tartifact \n\t\tCreates and uploads a platform artifact zip.") + print("\tprune\n\t\tDeletes old artifacts from bucket") + print("\tjson\n\t\tUpdate and upload nightly.json") + sys.exit(1) + else: + command = sys.argv[1].lower() + if command == "artifact": + if len(sys.argv) != 4: + print("Usage: {} artifact ".format(sys.argv[0])) + print("Error: Expected artifact command to be given platform prefix and artifact path.\n") + sys.exit(1) + + res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3]) + sys.exit(res) + + elif command == "prune": + res = prune_artifacts() + sys.exit(res) + + elif command == "json": + res = update_nightly_json() + sys.exit(res) \ No newline at end of file diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh deleted file mode 100755 index 8404b33ff..000000000 --- a/ci/upload_create_nightly.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -bucket=$1 -platform=$2 -artifact=$3 - -now=$(date +'%Y-%m-%d') -filename="odin-$platform-nightly+$now.zip" - -echo "Creating archive $filename from $artifact and uploading to $bucket" - -# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it. -if [ "${artifact: -4}" == ".zip" ] -then - echo "Artifact already a zip" - mkdir -p "output" - mv "$artifact" "output/$filename" -else - echo "Artifact needs to be zipped" - 7z a -bd "output/$filename" -r "$artifact" -fi - -b2 file upload "$bucket" "output/$filename" "nightly/$filename" From fcfc1cb97fedd2f25bed451c4093c64d25b3314d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Jun 2024 22:26:03 +0200 Subject: [PATCH 151/151] Nightly ZIP level 9 --- ci/nightly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/nightly.py b/ci/nightly.py index 7a23fd2c2..7bd32899d 100644 --- a/ci/nightly.py +++ b/ci/nightly.py @@ -1,6 +1,6 @@ import os import sys -from zipfile import ZipFile +from zipfile import ZipFile, ZIP_DEFLATED from b2sdk.v2 import InMemoryAccountInfo, B2Api from datetime import datetime import json @@ -40,7 +40,7 @@ def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") source_zip_name = destination_zip_name - with ZipFile(source_zip_name, 'w') as z: + with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z: for root, directory, filenames in os.walk(artifact): for file in filenames: file_path = os.path.join(root, file)

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