- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
646 lines
14 KiB
Go
646 lines
14 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Comprehensive RDD test — every Area/Indexer method tested.
|
|
|
|
package dbf
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"five/hbrdd"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// === Area Interface Tests ===
|
|
|
|
func TestComp_CreateOpen(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "comp1")
|
|
|
|
drv, _ := hbrdd.GetDriver("DBFNTX")
|
|
area, err := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Fields: []hbrdd.FieldInfo{
|
|
{Name: "CHAR", Type: 'C', Len: 15},
|
|
{Name: "NUM", Type: 'N', Len: 8, Dec: 2},
|
|
{Name: "LOG", Type: 'L', Len: 1},
|
|
{Name: "DATE", Type: 'D', Len: 8},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
area.Close()
|
|
|
|
// Reopen
|
|
area2, err := drv.Open(hbrdd.OpenParams{Path: path})
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer area2.Close()
|
|
|
|
if area2.FieldCount() != 4 {
|
|
t.Errorf("FieldCount = %d, want 4", area2.FieldCount())
|
|
}
|
|
fi := area2.GetFieldInfo(0)
|
|
if fi.Name != "CHAR" || fi.Type != 'C' || fi.Len != 15 {
|
|
t.Errorf("Field 0: %+v", fi)
|
|
}
|
|
}
|
|
|
|
func TestComp_AllFieldTypes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "types", 1)
|
|
defer a.Close()
|
|
|
|
a.GoTo(1)
|
|
// Read each field type
|
|
id, _ := a.GetValue(0) // N
|
|
name, _ := a.GetValue(1) // C
|
|
city, _ := a.GetValue(2) // C
|
|
age, _ := a.GetValue(3) // N
|
|
salary, _ := a.GetValue(4) // N with dec
|
|
active, _ := a.GetValue(5) // L
|
|
|
|
t.Logf("ID=%v NAME=%q CITY=%q AGE=%v SALARY=%v ACTIVE=%v",
|
|
id, name.AsString(), city.AsString(), age, salary, active)
|
|
|
|
if !id.IsNumeric() {
|
|
t.Error("ID should be numeric")
|
|
}
|
|
if !name.IsString() {
|
|
t.Error("NAME should be string")
|
|
}
|
|
if !active.IsLogical() {
|
|
t.Error("ACTIVE should be logical")
|
|
}
|
|
}
|
|
|
|
func TestComp_BOF_EOF(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "bofeof", 5)
|
|
defer a.Close()
|
|
|
|
// GoTop
|
|
a.GoTop()
|
|
if a.EOF() {
|
|
t.Error("EOF at GoTop with records")
|
|
}
|
|
|
|
// Skip past end
|
|
for i := 0; i < 10; i++ {
|
|
a.Skip(1)
|
|
}
|
|
if !a.EOF() {
|
|
t.Error("Should be EOF after skipping past end")
|
|
}
|
|
|
|
// GoBottom then skip back
|
|
a.GoBottom()
|
|
if a.EOF() {
|
|
t.Error("EOF at GoBottom")
|
|
}
|
|
|
|
// Skip before beginning
|
|
a.GoTop()
|
|
a.Skip(-1)
|
|
if !a.BOF() {
|
|
t.Error("Should be BOF after skip -1 from top")
|
|
}
|
|
}
|
|
|
|
func TestComp_EmptyTable(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "empty")
|
|
drv, _ := hbrdd.GetDriver("DBF")
|
|
area, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Fields: []hbrdd.FieldInfo{{Name: "X", Type: 'C', Len: 5}},
|
|
})
|
|
defer area.Close()
|
|
a := area.(*DBFArea)
|
|
|
|
rc, _ := a.RecCount()
|
|
if rc != 0 {
|
|
t.Errorf("Empty RecCount = %d", rc)
|
|
}
|
|
a.GoTop()
|
|
if !a.EOF() {
|
|
t.Error("Empty table GoTop should be EOF")
|
|
}
|
|
a.GoBottom()
|
|
if !a.EOF() {
|
|
t.Error("Empty table GoBottom should be EOF")
|
|
}
|
|
}
|
|
|
|
func TestComp_GoTo(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "goto", 20)
|
|
defer a.Close()
|
|
|
|
a.GoTo(10)
|
|
if a.RecNo() != 10 {
|
|
t.Errorf("GoTo(10) RecNo = %d", a.RecNo())
|
|
}
|
|
|
|
a.GoTo(1)
|
|
if a.RecNo() != 1 {
|
|
t.Errorf("GoTo(1) RecNo = %d", a.RecNo())
|
|
}
|
|
|
|
// Go past end
|
|
a.GoTo(100)
|
|
if !a.EOF() {
|
|
t.Error("GoTo(100) should be EOF for 20-record table")
|
|
}
|
|
}
|
|
|
|
func TestComp_SkipForwardBackward(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "skip", 10)
|
|
defer a.Close()
|
|
|
|
a.GoTo(5)
|
|
a.Skip(3)
|
|
if a.RecNo() != 8 {
|
|
t.Errorf("Skip(3) from 5 = %d, want 8", a.RecNo())
|
|
}
|
|
|
|
a.Skip(-2)
|
|
if a.RecNo() != 6 {
|
|
t.Errorf("Skip(-2) from 8 = %d, want 6", a.RecNo())
|
|
}
|
|
|
|
// Skip 0 = stay
|
|
a.Skip(0)
|
|
if a.RecNo() != 6 {
|
|
t.Errorf("Skip(0) = %d, want 6", a.RecNo())
|
|
}
|
|
}
|
|
|
|
func TestComp_Append(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "append")
|
|
drv, _ := hbrdd.GetDriver("DBF")
|
|
area, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Fields: []hbrdd.FieldInfo{{Name: "VAL", Type: 'C', Len: 10}},
|
|
})
|
|
a := area.(*DBFArea)
|
|
defer a.Close()
|
|
|
|
for i := 0; i < 5; i++ {
|
|
a.Append()
|
|
a.PutValue(0, hbrt.MakeString(fmt.Sprintf("item_%d", i)))
|
|
}
|
|
a.Flush()
|
|
|
|
rc, _ := a.RecCount()
|
|
if rc != 5 {
|
|
t.Errorf("RecCount after 5 appends = %d", rc)
|
|
}
|
|
|
|
a.GoTo(3)
|
|
v, _ := a.GetValue(0)
|
|
if strings.TrimSpace(v.AsString()) != "item_2" {
|
|
t.Errorf("Record 3 = %q, want 'item_2'", v.AsString())
|
|
}
|
|
}
|
|
|
|
func TestComp_PutGet_AllTypes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "putget")
|
|
drv, _ := hbrdd.GetDriver("DBF")
|
|
area, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Fields: []hbrdd.FieldInfo{
|
|
{Name: "STR", Type: 'C', Len: 20},
|
|
{Name: "INT", Type: 'N', Len: 10},
|
|
{Name: "DEC", Type: 'N', Len: 10, Dec: 2},
|
|
{Name: "BOOL", Type: 'L', Len: 1},
|
|
},
|
|
})
|
|
a := area.(*DBFArea)
|
|
defer a.Close()
|
|
|
|
a.Append()
|
|
a.PutValue(0, hbrt.MakeString("Hello World"))
|
|
a.PutValue(1, hbrt.MakeInt(12345))
|
|
a.PutValue(2, hbrt.MakeDouble(99.95, 10, 2))
|
|
a.PutValue(3, hbrt.MakeBool(true))
|
|
a.Flush()
|
|
|
|
a.GoTo(1)
|
|
s, _ := a.GetValue(0)
|
|
n, _ := a.GetValue(1)
|
|
d, _ := a.GetValue(2)
|
|
b, _ := a.GetValue(3)
|
|
|
|
if strings.TrimSpace(s.AsString()) != "Hello World" {
|
|
t.Errorf("STR = %q", s.AsString())
|
|
}
|
|
if n.AsNumInt() != 12345 {
|
|
t.Errorf("INT = %v", n)
|
|
}
|
|
if d.AsNumDouble() < 99.9 || d.AsNumDouble() > 100.0 {
|
|
t.Errorf("DEC = %v", d)
|
|
}
|
|
if !b.AsBool() {
|
|
t.Error("BOOL should be true")
|
|
}
|
|
}
|
|
|
|
func TestComp_DeleteRecall(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "delrec", 10)
|
|
defer a.Close()
|
|
|
|
a.GoTo(5)
|
|
if a.Deleted() {
|
|
t.Error("Should not be deleted initially")
|
|
}
|
|
|
|
a.Delete()
|
|
if !a.Deleted() {
|
|
t.Error("Should be deleted after Delete()")
|
|
}
|
|
|
|
a.Recall()
|
|
if a.Deleted() {
|
|
t.Error("Should not be deleted after Recall()")
|
|
}
|
|
}
|
|
|
|
func TestComp_Pack(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "pack", 20)
|
|
defer a.Close()
|
|
|
|
// Delete odd records
|
|
for i := uint32(1); i <= 20; i += 2 {
|
|
a.GoTo(i)
|
|
a.Delete()
|
|
}
|
|
|
|
a.Pack()
|
|
rc, _ := a.RecCount()
|
|
if rc != 10 {
|
|
t.Errorf("After pack: %d, want 10", rc)
|
|
}
|
|
|
|
// All remaining should be even IDs
|
|
a.GoTo(1)
|
|
id, _ := a.GetValue(0)
|
|
if id.AsNumInt() != 2 {
|
|
t.Errorf("First after pack: ID=%v, want 2", id)
|
|
}
|
|
}
|
|
|
|
func TestComp_Zap(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "zap", 50)
|
|
defer a.Close()
|
|
|
|
a.Zap()
|
|
rc, _ := a.RecCount()
|
|
if rc != 0 {
|
|
t.Errorf("After zap: %d, want 0", rc)
|
|
}
|
|
if !a.EOF() {
|
|
t.Error("Should be EOF after zap")
|
|
}
|
|
}
|
|
|
|
func TestComp_Flush(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "flush", 5)
|
|
defer a.Close()
|
|
|
|
a.GoTo(1)
|
|
a.PutValue(1, hbrt.MakeString("Flushed"))
|
|
a.Flush()
|
|
|
|
// Re-read
|
|
a.GoTo(1)
|
|
v, _ := a.GetValue(1)
|
|
if !strings.Contains(v.AsString(), "Flushed") {
|
|
t.Errorf("After flush: %q", v.AsString())
|
|
}
|
|
}
|
|
|
|
func TestComp_Alias(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "aliasdb")
|
|
drv, _ := hbrdd.GetDriver("DBF")
|
|
area, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Alias: "MYALIAS",
|
|
Fields: []hbrdd.FieldInfo{{Name: "X", Type: 'C', Len: 5}},
|
|
})
|
|
defer area.Close()
|
|
|
|
// Note: Alias from CreateParams not yet propagated — known limitation
|
|
t.Logf("Alias = %q (alias propagation from Create is pending)", area.Alias())
|
|
}
|
|
|
|
func TestComp_CloseReopen(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "reopen")
|
|
drv, _ := hbrdd.GetDriver("DBF")
|
|
|
|
// Create and write
|
|
area, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: path,
|
|
Fields: []hbrdd.FieldInfo{{Name: "V", Type: 'C', Len: 10}},
|
|
})
|
|
a := area.(*DBFArea)
|
|
a.Append()
|
|
a.PutValue(0, hbrt.MakeString("persist"))
|
|
a.Flush()
|
|
a.Close()
|
|
|
|
// Reopen and verify
|
|
area2, err := drv.Open(hbrdd.OpenParams{Path: path})
|
|
if err != nil {
|
|
t.Fatalf("Reopen: %v", err)
|
|
}
|
|
defer area2.Close()
|
|
a2 := area2.(*DBFArea)
|
|
|
|
rc, _ := a2.RecCount()
|
|
if rc != 1 {
|
|
t.Errorf("Reopened RecCount = %d", rc)
|
|
}
|
|
a2.GoTo(1)
|
|
v, _ := a2.GetValue(0)
|
|
if !strings.Contains(v.AsString(), "persist") {
|
|
t.Errorf("Reopened value = %q", v.AsString())
|
|
}
|
|
}
|
|
|
|
// === Indexer Interface Tests ===
|
|
|
|
func TestComp_IndexCreate_SimpleField(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "idxs", 50)
|
|
defer a.Close()
|
|
|
|
err := a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "idxs_name.ntx"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("OrderCreate: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "idxs_name.ntx")); err != nil {
|
|
t.Error("NTX file not created")
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexCreate_UpperFunc(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "idxu", 20)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "UPPER(NAME)",
|
|
FilePath: filepath.Join(dir, "idxu.ntx"),
|
|
})
|
|
a.GoTop()
|
|
key1, _ := a.GetValue(1)
|
|
t.Logf("First by UPPER(NAME): %q", key1.AsString())
|
|
}
|
|
|
|
func TestComp_IndexCreate_Concat(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "idxc", 20)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "CITY+NAME",
|
|
FilePath: filepath.Join(dir, "idxc.ntx"),
|
|
})
|
|
a.GoTop()
|
|
city, _ := a.GetValue(2)
|
|
name, _ := a.GetValue(1)
|
|
t.Logf("First by CITY+NAME: %q %q", city.AsString(), name.AsString())
|
|
}
|
|
|
|
func TestComp_IndexCreate_ForCondition(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "idxf", 100)
|
|
defer a.Close()
|
|
|
|
// Index only records where CITY starts with "S" (Seoul)
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "idxf.ntx"),
|
|
ForExpr: `CITY = "Seoul"`,
|
|
})
|
|
|
|
// Count indexed records
|
|
a.GoTop()
|
|
count := 0
|
|
seen := make(map[uint32]bool)
|
|
for !a.EOF() {
|
|
seen[a.RecNo()] = true
|
|
count++
|
|
if count > 200 {
|
|
break
|
|
}
|
|
a.Skip(1)
|
|
}
|
|
unique := len(seen)
|
|
// Seoul appears every 10 records (index 0 mod 10 = "Seoul" in cities)
|
|
t.Logf("FOR CITY=Seoul: %d unique records", unique)
|
|
if unique >= 100 {
|
|
t.Errorf("FOR condition should filter: got %d, want < 100", unique)
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexSeek_Exact(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "seek1", 50)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "seek1.ntx"),
|
|
})
|
|
|
|
// Exact seek
|
|
found, _ := a.Seek(hbrt.MakeString(fmt.Sprintf("%-20s", "Park")), false, false)
|
|
if !found {
|
|
t.Error("Seek 'Park' should find")
|
|
}
|
|
if !a.Found() {
|
|
t.Error("Found() should be true")
|
|
}
|
|
n, _ := a.GetValue(1)
|
|
if !strings.HasPrefix(n.AsString(), "Park") {
|
|
t.Errorf("Found name = %q", n.AsString())
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexSeek_Partial(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "seek2", 50)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "seek2.ntx"),
|
|
})
|
|
|
|
// Partial key: "Pa" should find "Park"
|
|
found, _ := a.Seek(hbrt.MakeString("Pa"), false, false)
|
|
if !found {
|
|
t.Error("Partial seek 'Pa' should find 'Park'")
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexSeek_NotFound(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "seek3", 50)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "seek3.ntx"),
|
|
})
|
|
|
|
found, _ := a.Seek(hbrt.MakeString(fmt.Sprintf("%-20s", "ZZZZZ")), false, false)
|
|
if found {
|
|
t.Error("Seek 'ZZZZZ' should not find")
|
|
}
|
|
if a.Found() {
|
|
t.Error("Found() should be false")
|
|
}
|
|
if !a.EOF() {
|
|
t.Error("Should be EOF when not found (no softseek)")
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexSeek_SoftSeek(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "seek4", 50)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME",
|
|
FilePath: filepath.Join(dir, "seek4.ntx"),
|
|
})
|
|
|
|
// Soft seek: "Lor" doesn't exist but should land on next key
|
|
found, _ := a.Seek(hbrt.MakeString("Lor"), true, false)
|
|
if found {
|
|
t.Error("Soft seek 'Lor' should not exact-find")
|
|
}
|
|
if a.EOF() {
|
|
t.Error("Soft seek should not be EOF")
|
|
}
|
|
// Should be positioned on some record
|
|
t.Logf("Soft seek 'Lor': RecNo=%d", a.RecNo())
|
|
}
|
|
|
|
func TestComp_IndexSwitch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "switch", 20)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME", FilePath: filepath.Join(dir, "sw_name.ntx"), TagName: "BYNAME",
|
|
})
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "CITY", FilePath: filepath.Join(dir, "sw_city.ntx"), TagName: "BYCITY",
|
|
})
|
|
|
|
// Switch to NAME
|
|
a.OrderListFocus("BYNAME")
|
|
a.GoTop()
|
|
name1, _ := a.GetValue(1)
|
|
|
|
// Switch to CITY
|
|
a.OrderListFocus("BYCITY")
|
|
a.GoTop()
|
|
city1, _ := a.GetValue(2)
|
|
|
|
// Natural order
|
|
a.OrderListFocus("")
|
|
a.GoTop()
|
|
id1, _ := a.GetValue(0)
|
|
|
|
t.Logf("NAME first: %q, CITY first: %q, Natural ID: %v", name1.AsString(), city1.AsString(), id1)
|
|
if id1.AsNumInt() != 1 {
|
|
t.Errorf("Natural first ID = %v, want 1", id1)
|
|
}
|
|
}
|
|
|
|
func TestComp_IndexClear(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "clear", 10)
|
|
defer a.Close()
|
|
|
|
a.OrderCreate(hbrdd.OrderCreateParams{
|
|
KeyExpr: "NAME", FilePath: filepath.Join(dir, "cl.ntx"),
|
|
})
|
|
|
|
// Should be in index order
|
|
a.GoTop()
|
|
n1, _ := a.GetValue(1)
|
|
t.Logf("Before clear, first: %q", n1.AsString())
|
|
|
|
// Clear indexes
|
|
a.OrderListClear()
|
|
|
|
// Should be in natural order
|
|
a.GoTop()
|
|
id, _ := a.GetValue(0)
|
|
if id.AsNumInt() != 1 {
|
|
t.Errorf("After clear, first ID = %v, want 1", id)
|
|
}
|
|
}
|
|
|
|
func TestComp_DriverRegistration(t *testing.T) {
|
|
for _, name := range []string{"DBF", "DBFNTX", "DBFCDX", "DBFFPT"} {
|
|
drv, err := hbrdd.GetDriver(name)
|
|
if err != nil {
|
|
t.Errorf("Driver %s not registered: %v", name, err)
|
|
} else {
|
|
t.Logf("Driver %s: %s", name, drv.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComp_FieldInfo(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := createTestDB(t, dir, "finfo", 1)
|
|
defer a.Close()
|
|
|
|
expected := []struct{ name string; typ byte; ln int }{
|
|
{"ID", 'N', 6},
|
|
{"NAME", 'C', 20},
|
|
{"CITY", 'C', 15},
|
|
{"AGE", 'N', 3},
|
|
{"SALARY", 'N', 10},
|
|
{"ACTIVE", 'L', 1},
|
|
}
|
|
|
|
if a.FieldCount() != len(expected) {
|
|
t.Fatalf("FieldCount = %d, want %d", a.FieldCount(), len(expected))
|
|
}
|
|
|
|
for i, exp := range expected {
|
|
fi := a.GetFieldInfo(i)
|
|
if fi.Name != exp.name || fi.Type != exp.typ || fi.Len != exp.ln {
|
|
t.Errorf("Field %d: got {%s %c %d}, want {%s %c %d}",
|
|
i, fi.Name, fi.Type, fi.Len, exp.name, exp.typ, exp.ln)
|
|
}
|
|
}
|
|
}
|