// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // CDX engine test — reads CDX files created by Harbour. package cdx import ( "os" "testing" ) const testCDX = "/mnt/d/charles/five/dbf/cdxtest.cdx" func TestCDXOpen(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found — run Harbour to create it first") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() t.Logf("CDX opened: %d tags", idx.TagCount()) if idx.TagCount() < 1 { t.Fatal("Expected at least 1 tag") } for i := 0; i < idx.TagCount(); i++ { tag := idx.GetTag(i) if tag != nil { t.Logf(" Tag %d: name=%q", i, tag.Name) } } } func TestCDXFindTag(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("NAME") if tag == nil { // Try without prefix for i := 0; i < idx.TagCount(); i++ { tg := idx.GetTag(i) t.Logf("Available tag: %q", tg.Name) } t.Fatal("Tag BYNAME not found") } t.Logf("Found tag BYNAME") } func TestCDXGoTop(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("NAME") if tag == nil { // Try first tag if idx.TagCount() > 0 { tag = idx.GetTag(0) t.Logf("Using first tag: %q", tag.Name) } else { t.Skip("No tags found") } } ok := tag.GoTop() t.Logf("GoTop: ok=%v, recNo=%d, key=%q, eof=%v", ok, tag.CurRecNo(), string(tag.CurKey()), tag.IsEOF()) if tag.IsEOF() { t.Error("GoTop resulted in EOF") } // First by name should be "Cho" (Harbour verified) key := string(tag.CurKey()) t.Logf("First key: %q", key) } func TestCDXTraverse(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("NAME") if tag == nil && idx.TagCount() > 0 { tag = idx.GetTag(0) } if tag == nil { t.Skip("No tags") } tag.GoTop() count := 0 prev := "" for !tag.IsEOF() { key := string(tag.CurKey()) if key < prev { t.Errorf("Not sorted: %q < %q at %d", key, prev, count) break } prev = key count++ if count > 200 { t.Error("Too many iterations, possible infinite loop") break } tag.SkipNext() } t.Logf("Traversed %d keys", count) if count != 100 { t.Errorf("Expected 100 keys, got %d", count) } } func TestCDXSeek(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("NAME") if tag == nil && idx.TagCount() > 0 { tag = idx.GetTag(0) } if tag == nil { t.Skip("No tags") } // Seek "Park" — should find it searchKey := make([]byte, 20) copy(searchKey, []byte("Park")) for i := 4; i < 20; i++ { searchKey[i] = ' ' } recNo, found := tag.Seek(searchKey) t.Logf("Seek 'Park': found=%v recNo=%d", found, recNo) if !found { t.Error("Expected to find 'Park'") } } func TestCDXGoBottom(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("NAME") if tag == nil && idx.TagCount() > 0 { tag = idx.GetTag(0) } if tag == nil { t.Skip("No tags") } tag.GoBottom() t.Logf("GoBottom: recNo=%d key=%q", tag.CurRecNo(), string(tag.CurKey())) // Last by name should be "Yoon" (Harbour verified) key := string(tag.CurKey()) if len(key) >= 4 && key[:4] != "Yoon" { t.Errorf("Last key = %q, expected 'Yoon...'", key) } } func TestCDXCityTag(t *testing.T) { if _, err := os.Stat(testCDX); err != nil { t.Skip("cdxtest.cdx not found") } idx, err := OpenIndex(testCDX) if err != nil { t.Fatalf("OpenIndex: %v", err) } defer idx.Close() tag := idx.FindTag("CITY") if tag == nil { t.Skip("BYCITY tag not found") } tag.GoTop() t.Logf("City GoTop: recNo=%d key=%q", tag.CurRecNo(), string(tag.CurKey())) // First by city should be "London" (Harbour verified) key := string(tag.CurKey()) if len(key) >= 6 && key[:6] != "London" { t.Logf("First city key = %q (expected 'London...')", key) } }