Something went wrong. Try again.
Go bindings for libghostty-vt. pkg.go.dev/go.mitchellh.com/libghostty
ghostty
Something went wrong. Try again.
go-libghostty get_multi_test.go
11 kB · 429 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430package libghostty
import ( "runtime" "testing" "time" "unsafe")
// The tests in this file cover getMultiArgs and every public GetMulti// that is not already tested elsewhere. Each GetMulti test reads at// least two keys, and the second key never has its zero value. That way// the test fails if any key after the first is dropped or misread.//// These GetMulti callers are tested elsewhere:// - KittyGraphicsImage.Info: TestKittyGraphicsImageInfo// - KittyGraphicsPlacementIterator.Info: TestKittyGraphicsPlacementInfo// - SelectionGesture.GetMulti: TestSelectionGestureAPIs// - Search.GetMulti: TestSearchLifecycleAndResults// - SnapshotDecoder.GetMulti, through Progress: TestSnapshotIncrementalDecode
// getMultiGlobal gives TestGetMultiArgsValueKinds a pointer to a Go// global.var getMultiGlobal uint64
// TestGetMultiArgsValueKinds checks that alloc accepts every kind of// pointer a caller may pass and stores each one unchanged. Pinning only// applies to Go heap memory, so the other kinds must pass through.func TestGetMultiArgsValueKinds(t *testing.T) { heap := new(uint64) cmem := Alloc(8) if cmem == nil { t.Fatal("allocation failed") } defer Free(cmem, 8)
values := []unsafe.Pointer{ unsafe.Pointer(heap), unsafe.Pointer(&getMultiGlobal), cmem, nil, }
var args getMultiArgs if err := args.alloc(values); err != nil { t.Fatal(err) } defer args.free()
got := unsafe.Slice(args.values, len(values)) for i := range values { if got[i] != values[i] { t.Fatalf("value %d: got %p, want %p", i, got[i], values[i]) } }}
// TestGetMultiArgsEmpty checks that alloc with no values allocates// nothing and that free then does nothing.func TestGetMultiArgsEmpty(t *testing.T) { var args getMultiArgs if err := args.alloc(nil); err != nil { t.Fatal(err) } if args.values != nil { t.Fatal("expected no allocation for empty values") } args.free()}
// TestAllocWithKeys checks that allocWithKeys converts the keys to the// C type and stores them right after the output pointers.func TestAllocWithKeys(t *testing.T) { var a, b uint16 keys := []TerminalData{TerminalDataCols, TerminalDataRows} values := []unsafe.Pointer{unsafe.Pointer(&a), unsafe.Pointer(&b)}
var args getMultiArgs cKeys, err := allocWithKeys[int32](&args, keys, values) if err != nil { t.Fatal(err) } defer args.free()
ptrSize := unsafe.Sizeof(unsafe.Pointer(nil)) if want := uintptr(len(keys)) * (ptrSize + 4); args.size != want { t.Fatalf("expected block size %d, got %d", want, args.size) } if want := unsafe.Add(unsafe.Pointer(args.values), 2*ptrSize); unsafe.Pointer(cKeys) != want { t.Fatal("expected keys to follow the output pointers") } for i, key := range unsafe.Slice(cKeys, len(keys)) { if int(key) != int(keys[i]) { t.Fatalf("key %d: got %d, want %d", i, key, keys[i]) } }}
// TestGetMultiArgsFreeZeroes checks that free zeroes the whole block,// keys included, before freeing it. The test hook runs after the// zeroing and before the free, so the test never reads freed memory.func TestGetMultiArgsFreeZeroes(t *testing.T) { var calls int testHookGetMultiFree = func(block unsafe.Pointer, size uintptr) { calls++ for i, b := range unsafe.Slice((*byte)(block), size) { if b != 0 { t.Errorf("byte %d of %d not zeroed before free: %#x", i, size, b) return } } } defer func() { testHookGetMultiFree = nil }()
var a, b uint16 var args getMultiArgs if _, err := allocWithKeys[int32]( &args, []TerminalData{TerminalDataCols, TerminalDataRows}, []unsafe.Pointer{unsafe.Pointer(&a), unsafe.Pointer(&b)}, ); err != nil { t.Fatal(err) } args.free() if calls != 1 { t.Fatalf("expected the free hook to run once, ran %d times", calls) } if args.values != nil || args.size != 0 { t.Fatal("expected free to reset the block") }
// A second free has nothing left to zero or free. args.free() if calls != 1 { t.Fatalf("expected a second free to do nothing, hook ran %d times", calls) }}
// TestGetMultiArgsKeepsValuesUntilFree checks that a value stays alive// while getMultiArgs holds it, even with no other Go reference to it,// and can be collected after free.//// runtime.Pinner cannot report whether an object is pinned, so this is// the closest the test can get. It cannot tell pinning apart from the// Pinner simply holding a reference, because both keep the object// alive. To check the pinning itself, run the tests with// GOEXPERIMENT=cgocheck2. The runtime then stops the program if Go// stores an unpinned pointer in C memory.func TestGetMultiArgsKeepsValuesUntilFree(t *testing.T) { var args getMultiArgs collected := make(chan struct{}) func() { // The object is only reachable from this function. Once it // returns, only args and the C block refer to it, and the // garbage collector does not look inside C memory. obj := new([64]byte) runtime.AddCleanup(obj, func(ch chan struct{}) { close(ch) }, collected) if err := args.alloc([]unsafe.Pointer{unsafe.Pointer(obj)}); err != nil { t.Fatal(err) } }()
for range 5 { runtime.GC() } select { case <-collected: t.Fatal("value was collected while getMultiArgs held it") case <-time.After(50 * time.Millisecond): }
args.free()
deadline := time.After(5 * time.Second) for { runtime.GC() select { case <-collected: return case <-deadline: t.Fatal("value was not collected after free") case <-time.After(10 * time.Millisecond): } }}
// TestTerminalGetMulti checks Terminal.GetMulti.func TestTerminalGetMulti(t *testing.T) { term, err := NewTerminal(WithSize(80, 24)) if err != nil { t.Fatal(err) } defer term.Close()
var cols, rows uint16 if err := term.GetMulti( []TerminalData{TerminalDataCols, TerminalDataRows}, []unsafe.Pointer{unsafe.Pointer(&cols), unsafe.Pointer(&rows)}, ); err != nil { t.Fatal(err) } if cols != 80 || rows != 24 { t.Fatalf("expected 80x24, got %dx%d", cols, rows) }}
// TestCellAndRowGetMulti checks Cell.GetMulti and Row.GetMulti.func TestCellAndRowGetMulti(t *testing.T) { term, err := NewTerminal(WithSize(10, 3)) if err != nil { t.Fatal(err) } defer term.Close()
// Twelve bold characters on a ten column screen: the first row is // styled and soft wraps into the second. term.VTWrite([]byte("\x1b[1mABCDEFGHIJKL"))
ref, err := term.GridRef(Point{Tag: PointTagActive, X: 0, Y: 0}) if err != nil { t.Fatal(err) }
cell, err := ref.Cell() if err != nil { t.Fatal(err) } var codepoint uint32 var hasText, hasStyling bool if err := cell.GetMulti( []CellData{CellDataCodepoint, CellDataHasText, CellDataHasStyling}, []unsafe.Pointer{ unsafe.Pointer(&codepoint), unsafe.Pointer(&hasText), unsafe.Pointer(&hasStyling), }, ); err != nil { t.Fatal(err) } if codepoint != 'A' || !hasText || !hasStyling { t.Fatalf( "unexpected cell values: codepoint=%U has_text=%t has_styling=%t", codepoint, hasText, hasStyling, ) }
row, err := ref.Row() if err != nil { t.Fatal(err) } var wrap, styled bool if err := row.GetMulti( []RowData{RowDataWrap, RowDataStyled}, []unsafe.Pointer{unsafe.Pointer(&wrap), unsafe.Pointer(&styled)}, ); err != nil { t.Fatal(err) } if !wrap || !styled { t.Fatalf("unexpected row values: wrap=%t styled=%t", wrap, styled) }}
// TestRenderStateGetMulti checks RenderState.GetMulti,// RenderStateRowIterator.GetMulti and RenderStateRowCells.GetMulti.func TestRenderStateGetMulti(t *testing.T) { term, err := NewTerminal(WithSize(80, 24)) if err != nil { t.Fatal(err) } defer term.Close() term.VTWrite([]byte("first\r\n\x1b[1mhello"))
rs, err := NewRenderState() if err != nil { t.Fatal(err) } defer rs.Close() if err := rs.Update(term); err != nil { t.Fatal(err) }
var cols, rows uint16 if err := rs.GetMulti( []RenderStateData{RenderStateDataCols, RenderStateDataRows}, []unsafe.Pointer{unsafe.Pointer(&cols), unsafe.Pointer(&rows)}, ); err != nil { t.Fatal(err) } if cols != 80 || rows != 24 { t.Fatalf("expected 80x24, got %dx%d", cols, rows) }
ri, err := NewRenderStateRowIterator() if err != nil { t.Fatal(err) } defer ri.Close() if err := rs.RowIterator(ri); err != nil { t.Fatal(err) }
// Move to the second row, so its viewport position is not zero. if !ri.Next() || !ri.Next() { t.Fatal("expected at least two rows") } wantDirty, err := ri.Dirty() if err != nil { t.Fatal(err) } var dirty bool var viewportY int32 if err := ri.GetMulti( []RenderStateRowData{RenderStateRowDataDirty, RenderStateRowDataViewportY}, []unsafe.Pointer{unsafe.Pointer(&dirty), unsafe.Pointer(&viewportY)}, ); err != nil { t.Fatal(err) } if dirty != wantDirty || viewportY != 1 { t.Fatalf( "unexpected row values: dirty=%t (want %t) viewport_y=%d (want 1)", dirty, wantDirty, viewportY, ) }
rc, err := NewRenderStateRowCells() if err != nil { t.Fatal(err) } defer rc.Close() if err := ri.Cells(rc); err != nil { t.Fatal(err) } if !rc.Next() { t.Fatal("expected at least one cell") } var graphemesLen uint32 var hasStyling bool if err := rc.GetMulti( []RenderStateRowCellsData{ RenderStateRowCellsDataGraphemesLen, RenderStateRowCellsDataHasStyling, }, []unsafe.Pointer{unsafe.Pointer(&graphemesLen), unsafe.Pointer(&hasStyling)}, ); err != nil { t.Fatal(err) } if graphemesLen != 1 || !hasStyling { t.Fatalf( "unexpected cell values: graphemes_len=%d has_styling=%t", graphemesLen, hasStyling, ) }}
// TestKittyGraphicsGetMulti checks KittyGraphicsImage.GetMulti and// KittyGraphicsPlacementIterator.GetMulti.func TestKittyGraphicsGetMulti(t *testing.T) { term := newKittyTerminal(t) defer term.Close() sendKittyImage(t, term)
kg, err := term.KittyGraphics() if err != nil { t.Fatal(err) } iter, err := NewKittyGraphicsPlacementIterator() if err != nil { t.Fatal(err) } defer iter.Close() if err := kg.PlacementIterator(iter); err != nil { t.Fatal(err) } if !iter.Next() { t.Fatal("expected at least one placement") }
wantImageID, err := iter.ImageID() if err != nil { t.Fatal(err) } wantPlacementID, err := iter.PlacementID() if err != nil { t.Fatal(err) } // The image ID goes second because it is never zero. var placementID, imageID uint32 if err := iter.GetMulti( []KittyGraphicsPlacementData{ KittyGraphicsPlacementDataPlacementID, KittyGraphicsPlacementDataImageID, }, []unsafe.Pointer{unsafe.Pointer(&placementID), unsafe.Pointer(&imageID)}, ); err != nil { t.Fatal(err) } if imageID != wantImageID || placementID != wantPlacementID { t.Fatalf( "unexpected placement values: image_id=%d (want %d) placement_id=%d (want %d)", imageID, wantImageID, placementID, wantPlacementID, ) }
img := kg.Image(wantImageID) if img == nil { t.Fatal("expected non-nil image") } var id, width, height uint32 if err := img.GetMulti( []KittyGraphicsImageData{ KittyGraphicsImageDataID, KittyGraphicsImageDataWidth, KittyGraphicsImageDataHeight, }, []unsafe.Pointer{ unsafe.Pointer(&id), unsafe.Pointer(&width), unsafe.Pointer(&height), }, ); err != nil { t.Fatal(err) } if id != wantImageID || width != 1 || height != 1 { t.Fatalf( "unexpected image values: id=%d (want %d) size=%dx%d (want 1x1)", id, wantImageID, width, height, ) }}