mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-28 18:30:06 +00:00
Merge branch 'master' into fix-fmt-compquat-sign
This commit is contained in:
@@ -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) {
|
||||
|
||||
+35
-35
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ";<text>" ST.
|
||||
HYPERLINK :: "8" // Followed by ";[params];<URI>" ST. Closed by OSC HYPERLINK ";;" ST.
|
||||
CLIPBOARD :: "52" // Followed by ";c;<Base64-encoded string>" ST.
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+16
-15
@@ -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)
|
||||
}
|
||||
+20
-22
@@ -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 := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
||||
read :: proc(data: []byte, filename := "<input>", 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 := "<input>", 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 := "<input>", 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 := "<input>", 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 := "<input>", 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 := "<input>", print_error := false, allocato
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//+build !freestanding
|
||||
package log
|
||||
|
||||
import "core:encoding/ansi"
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:os"
|
||||
@@ -70,18 +71,10 @@ 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 {
|
||||
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)
|
||||
@@ -99,12 +92,12 @@ 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"
|
||||
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 {
|
||||
@@ -125,6 +118,24 @@ 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.sbprint(buf, " ")
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
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 :: 2 * Gigabyte
|
||||
|
||||
|
||||
Rollback_Stack_Header :: bit_field u64 {
|
||||
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: uintptr,
|
||||
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 := raw_data(block.buffer)
|
||||
end := start[block.offset:]
|
||||
return start < ptr && 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 = raw_data(block.buffer)[header.prev_ptr:]
|
||||
header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[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 {
|
||||
// `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) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
allocated_new_block: bool
|
||||
}
|
||||
|
||||
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
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
allocated_new_block = true
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if allocated_new_block {
|
||||
panic("Rollback Stack Allocator allocated a new block but did not use it.")
|
||||
}
|
||||
}
|
||||
parent = block
|
||||
continue
|
||||
}
|
||||
|
||||
header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
|
||||
ptr := start[padding:]
|
||||
|
||||
header^ = {
|
||||
prev_offset = block.offset,
|
||||
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 + 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 = cast(uintptr)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, 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.", location)
|
||||
|
||||
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 : 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)
|
||||
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
|
||||
|
||||
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(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 {
|
||||
zero_slice(result)
|
||||
}
|
||||
|
||||
case .Free:
|
||||
err = rb_free(stack, old_memory)
|
||||
|
||||
case .Free_All:
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () ---
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
//+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
|
||||
|
||||
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 :: (MAX_PROGRESS_WIDTH * 8 + 224) * 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,
|
||||
progress_width: 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
report.test_column_len = max(report.test_column_len, len(it.name))
|
||||
}
|
||||
|
||||
// Handle the last (or only) 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
|
||||
}
|
||||
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))
|
||||
|
||||
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, report: Report, 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 - (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.
|
||||
//
|
||||
// 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 ..< report.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, report, &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 {
|
||||
// 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,
|
||||
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)
|
||||
}
|
||||
}
|
||||
+791
-44
@@ -1,73 +1,820 @@
|
||||
//+private
|
||||
package testing
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:bytes"
|
||||
import "core:encoding/ansi"
|
||||
@require import "core:encoding/base64"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
@require import pkg_log "core:log"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:slice"
|
||||
@require import "core:strings"
|
||||
import "core:sync/chan"
|
||||
import "core:thread"
|
||||
import "core:time"
|
||||
|
||||
reset_t :: proc(t: ^T) {
|
||||
clear(&t.cleanups)
|
||||
t.error_count = 0
|
||||
// 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, 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)
|
||||
// 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)
|
||||
// Copy failed tests to the clipboard when done.
|
||||
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)
|
||||
// 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 {
|
||||
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)
|
||||
|
||||
setup_task_signal_handler(task.user_index)
|
||||
|
||||
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.assertion_failure_proc = test_assertion_failure_proc
|
||||
|
||||
context.logger = {
|
||||
procedure = test_logger_proc,
|
||||
data = &data.t,
|
||||
lowest_level = get_log_level(),
|
||||
options = Default_Test_Logger_Opts,
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
|
||||
data.it.p(&data.t)
|
||||
|
||||
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_NAMES != "" {
|
||||
select_internal_tests: [dynamic]Internal_Test
|
||||
defer delete(select_internal_tests)
|
||||
|
||||
{
|
||||
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)
|
||||
|
||||
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 <nil> 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.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)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// persist log messages past their lifetimes. It has its own variable name
|
||||
// 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 := context.allocator
|
||||
|
||||
context.logger = {
|
||||
procedure = runner_logger_proc,
|
||||
data = &log_messages,
|
||||
lowest_level = get_log_level(),
|
||||
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
|
||||
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
|
||||
|
||||
#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 {
|
||||
signals_were_raised := false
|
||||
|
||||
redraw_report(stdout, report)
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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 := memory_is_in_bad_state
|
||||
}
|
||||
|
||||
if should_report {
|
||||
write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name)
|
||||
|
||||
pkg_log.log(.Warning if memory_is_in_bad_state else .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.seed = shared_random_seed
|
||||
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_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 {
|
||||
bypass_progress_overwrite = true
|
||||
signals_were_raised = true
|
||||
}
|
||||
|
||||
total_failure_count += 1
|
||||
total_done_count += 1
|
||||
}
|
||||
|
||||
// -- Redraw.
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
if len(log_messages) == 0 && !needs_to_redraw(report) {
|
||||
continue main_loop
|
||||
}
|
||||
|
||||
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)
|
||||
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.
|
||||
|
||||
// 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)
|
||||
|
||||
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,
|
||||
"Finished %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: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)
|
||||
}
|
||||
|
||||
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: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]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
//+private
|
||||
//+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) {
|
||||
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
//+private
|
||||
//+build windows
|
||||
package testing
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//+private
|
||||
//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
package testing
|
||||
|
||||
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
|
||||
|
||||
@(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")
|
||||
stop_runner_callback :: proc "c" (sig: libc.int) {
|
||||
intrinsics.atomic_store(&stop_runner_flag, 1)
|
||||
}
|
||||
|
||||
@(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[1 + i:len(sigbuf) - 1]
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_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
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//+private
|
||||
//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku
|
||||
package testing
|
||||
|
||||
_setup_signal_handler :: proc() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
_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
|
||||
}
|
||||
+43
-26
@@ -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,55 @@ Internal_Test :: struct {
|
||||
Internal_Cleanup :: struct {
|
||||
procedure: proc(rawptr),
|
||||
user_data: rawptr,
|
||||
ctx: runtime.Context,
|
||||
}
|
||||
|
||||
T :: struct {
|
||||
error_count: int,
|
||||
|
||||
w: io.Writer,
|
||||
// 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,
|
||||
|
||||
// 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 +81,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 +116,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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+118
-15
@@ -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,94 @@ 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
|
||||
data.task = {}
|
||||
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
|
||||
data.task = {}
|
||||
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) {
|
||||
intrinsics.atomic_store(&pool.is_running, false)
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user