Files
Nim/tests/gc/gctest.nim
metagn 720d0aee5c add retries to testament, use it for GC tests (#24279)
Testament now retries a test by a specified amount if it fails in any
way other than an invalid spec. This is to deal with the flaky GC tests
on Windows CI that fail in many different ways, from the linker randomly
erroring, segfaults, etc.

Unfortunately I couldn't do this cleanly in testament's current code.
The proc `addResult`, which is the "final" proc called in a test run's
lifetime, is now wrapped in a proc `finishTest` that returns a bool
`true` if the test failed and has to be retried. This result is
propagated up from `cmpMsgs` and `compilerOutputTests` until it reaches
`testSpecHelper`, which handles these results by recursing if the test
has to be retried. Since calling `testSpecHelper` means "run this test
with one given configuration", this means every single matrix
option/target etc. receive an equal amount of retries each.

The result of `finishTest` is ignored in cases where it's known that it
won't be retried due to passing, being skipped, having an invalid spec
etc. It's also ignored in `testNimblePackages` because it's not
necessary for those specific tests yet and similar retry behavior is
already implemented for part of it.

This was a last resort for the flaky GC tests but they've been a problem
for years at this point, they give us more work to do and turn off
contributors. Ideally GC tests failing should mark as "needs review" in
the CI rather than "failed" but I don't know if Github supports
something like this.
2024-10-12 22:48:44 +02:00

212 lines
4.9 KiB
Nim

discard """
outputsub: "finished"
retries: 2
"""
# Test the garbage collector.
import
strutils
type
PNode = ref TNode
TNode {.final.} = object
le, ri: PNode
data: string
TTable {.final.} = object
counter, max: int
data: seq[string]
TBNode {.final.} = object
other: PNode # a completely different tree
data: string
sons: seq[TBNode] # directly embedded!
t: TTable
TCaseKind = enum nkStr, nkWhole, nkList
PCaseNode = ref TCaseNode
TCaseNode {.final.} = object
case kind: TCaseKind
of nkStr: data: string
of nkList: sons: seq[PCaseNode]
else: unused: seq[string]
TIdObj* = object of RootObj
id*: int # unique id; use this for comparisons and not the pointers
PIdObj* = ref TIdObj
PIdent* = ref TIdent
TIdent*{.acyclic.} = object of TIdObj
s*: string
next*: PIdent # for hash-table chaining
h*: int # hash value of s
var
flip: int
proc newCaseNode(data: string): PCaseNode =
if flip == 0:
result = PCaseNode(kind: nkStr, data: data)
else:
result = PCaseNode(kind: nkWhole, unused: @["", "abc", "abdc"])
flip = 1 - flip
proc newCaseNode(a, b: PCaseNode): PCaseNode =
result = PCaseNode(kind: nkList, sons: @[a, b])
proc caseTree(lvl: int = 0): PCaseNode =
if lvl == 3: result = newCaseNode("data item")
else: result = newCaseNode(caseTree(lvl+1), caseTree(lvl+1))
proc finalizeNode(n: PNode) =
assert(n != nil)
write(stdout, "finalizing: ")
writeLine(stdout, "not nil")
var
id: int = 1
proc buildTree(depth = 1): PNode =
if depth == 7: return nil
new(result, finalizeNode)
result.le = buildTree(depth+1)
result.ri = buildTree(depth+1)
result.data = $id
inc(id)
proc returnTree(): PNode =
writeLine(stdout, "creating id: " & $id)
new(result, finalizeNode)
result.data = $id
new(result.le, finalizeNode)
result.le.data = $id & ".1"
new(result.ri, finalizeNode)
result.ri.data = $id & ".2"
inc(id)
# now create a cycle:
writeLine(stdout, "creating id (cyclic): " & $id)
var cycle: PNode
new(cycle, finalizeNode)
cycle.data = $id
cycle.le = cycle
cycle.ri = cycle
inc(id)
#writeLine(stdout, "refcount: " & $refcount(cycle))
#writeLine(stdout, "refcount le: " & $refcount(cycle.le))
#writeLine(stdout, "refcount ri: " & $refcount(cycle.ri))
proc printTree(t: PNode) =
if t == nil: return
writeLine(stdout, "printing")
writeLine(stdout, t.data)
printTree(t.le)
printTree(t.ri)
proc unsureNew(result: var PNode) =
writeLine(stdout, "creating unsure id: " & $id)
new(result, finalizeNode)
result.data = $id
new(result.le, finalizeNode)
result.le.data = $id & ".a"
new(result.ri, finalizeNode)
result.ri.data = $id & ".b"
inc(id)
proc setSons(n: var TBNode) =
n.sons = @[] # free memory of the sons
n.t.data = @[]
var
m: seq[string]
m = @[]
setLen(m, len(n.t.data) * 2)
for i in 0..high(m):
m[i] = "..."
n.t.data = m
proc buildBTree(father: var TBNode) =
father.data = "father"
father.other = nil
father.sons = @[]
for i in 1..10:
write(stdout, "next iteration!\n")
var n: TBNode
n.other = returnTree()
n.data = "B node: " & $i
if i mod 2 == 0: n.sons = @[] # nil and [] need to be handled correctly!
add father.sons, n
father.t.counter = 0
father.t.max = 3
father.t.data = @["ha", "lets", "stress", "it"]
setSons(father)
proc getIdent(identifier: cstring, length: int, h: int): PIdent =
new(result)
result.h = h
result.s = newString(length)
proc main() =
discard getIdent("addr", 4, 0)
discard getIdent("hall", 4, 0)
discard getIdent("echo", 4, 0)
discard getIdent("huch", 4, 0)
var
father: TBNode
for i in 1..1_00:
buildBTree(father)
for i in 1..1_00:
var t = returnTree()
var t2: PNode
unsureNew(t2)
write(stdout, "now building bigger trees: ")
var t2: PNode
for i in 1..100:
t2 = buildTree()
printTree(t2)
write(stdout, "now test sequences of strings:")
var s: seq[string] = @[]
for i in 1..100:
add s, "hohoho" # test reallocation
writeLine(stdout, s[89])
write(stdout, "done!\n")
var
father {.threadvar.}: TBNode
s {.threadvar.}: string
fatherAsGlobal: TBNode
proc start =
s = ""
s = ""
writeLine(stdout, repr(caseTree()))
father.t.data = @["ha", "lets", "stress", "it"]
father.t.data = @["ha", "lets", "stress", "it"]
var t = buildTree()
write(stdout, repr(t[]))
buildBTree(father)
write(stdout, repr(father))
write(stdout, "starting main...\n")
main()
GC_fullCollect()
# the M&S GC fails with this call and it's unclear why. Definitely something
# we need to fix!
#GC_fullCollect()
writeLine(stdout, GC_getStatistics())
write(stdout, "finished\n")
fatherAsGlobal.t.data = @["ha", "lets", "stress", "it"]
var tg = buildTree()
buildBTree(fatherAsGlobal)
var thr: array[8, Thread[void]]
for i in low(thr)..high(thr):
createThread(thr[i], start)
joinThreads(thr)
start()