Files
five/hbrdd/dbf/dbf_test.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

371 lines
7.0 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
package dbf
import (
"five/hbrt"
"five/hbrdd"
"os"
"path/filepath"
"testing"
)
func tempDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
return dir
}
func TestCreateAndOpenDBF(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test.dbf")
// Create
drv := &DBFDriver{}
area, err := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "NAME", Type: 'C', Len: 20},
{Name: "AGE", Type: 'N', Len: 5, Dec: 0},
{Name: "ACTIVE", Type: 'L', Len: 1},
},
})
if err != nil {
t.Fatal(err)
}
area.Close()
// Verify file exists
if _, err := os.Stat(path); err != nil {
t.Fatal("DBF file not created")
}
// Open
area2, err := drv.Open(hbrdd.OpenParams{Path: path})
if err != nil {
t.Fatal(err)
}
defer area2.Close()
if area2.FieldCount() != 3 {
t.Errorf("field count = %d, want 3", area2.FieldCount())
}
if area2.EOF() != true {
t.Error("empty table should be EOF")
}
}
func TestAppendAndRead(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, err := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "NAME", Type: 'C', Len: 20},
{Name: "AGE", Type: 'N', Len: 5, Dec: 0},
{Name: "SALARY", Type: 'N', Len: 10, Dec: 2},
},
})
if err != nil {
t.Fatal(err)
}
// Append 3 records
for _, rec := range []struct {
name string
age int
salary float64
}{
{"Kim", 30, 50000.00},
{"Lee", 25, 45000.50},
{"Park", 35, 60000.75},
} {
area.Append()
area.PutValue(0, hbrt.MakeString(rec.name))
area.PutValue(1, hbrt.MakeInt(rec.age))
area.PutValue(2, hbrt.MakeDouble(rec.salary, 10, 2))
area.Flush()
}
area.Close()
// Reopen and verify
area2, err := drv.Open(hbrdd.OpenParams{Path: path})
if err != nil {
t.Fatal(err)
}
defer area2.Close()
rc, _ := area2.RecCount()
if rc != 3 {
t.Fatalf("reccount = %d, want 3", rc)
}
// Read record 1
area2.GoTo(1)
name, _ := area2.GetValue(0)
age, _ := area2.GetValue(1)
salary, _ := area2.GetValue(2)
if got := name.AsString(); got[:3] != "Kim" {
t.Errorf("rec 1 name = %q, want Kim...", got)
}
if age.AsNumInt() != 30 {
t.Errorf("rec 1 age = %d, want 30", age.AsNumInt())
}
if salary.AsDouble() != 50000.00 {
t.Errorf("rec 1 salary = %f, want 50000.00", salary.AsDouble())
}
// Read record 3
area2.GoTo(3)
name, _ = area2.GetValue(0)
if got := name.AsString(); got[:4] != "Park" {
t.Errorf("rec 3 name = %q, want Park...", got)
}
}
func TestGoTopSkipEOF(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, _ := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "ID", Type: 'N', Len: 5},
},
})
for i := 1; i <= 5; i++ {
area.Append()
area.PutValue(0, hbrt.MakeInt(i))
area.Flush()
}
area.Close()
area2, _ := drv.Open(hbrdd.OpenParams{Path: path})
defer area2.Close()
// GO TOP
area2.GoTop()
if area2.RecNo() != 1 {
t.Errorf("GoTop recno = %d, want 1", area2.RecNo())
}
if area2.BOF() {
t.Error("GoTop should not be BOF")
}
// SKIP forward
for i := 0; i < 4; i++ {
area2.Skip(1)
}
if area2.RecNo() != 5 {
t.Errorf("after 4 skips recno = %d, want 5", area2.RecNo())
}
// SKIP past last
area2.Skip(1)
if !area2.EOF() {
t.Error("should be EOF after skipping past last record")
}
// GO BOTTOM
area2.GoBottom()
if area2.RecNo() != 5 {
t.Errorf("GoBottom recno = %d, want 5", area2.RecNo())
}
// SKIP backward to BOF
for i := 0; i < 5; i++ {
area2.Skip(-1)
}
if !area2.BOF() {
t.Error("should be BOF after skipping backward past first record")
}
}
func TestDeleteRecallPack(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, _ := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "ID", Type: 'N', Len: 5},
},
})
for i := 1; i <= 5; i++ {
area.Append()
area.PutValue(0, hbrt.MakeInt(i))
area.Flush()
}
// Delete record 2 and 4
area.GoTo(2)
area.Delete()
area.Flush()
area.GoTo(4)
area.Delete()
area.Flush()
// Verify deleted
area.GoTo(2)
if !area.Deleted() {
t.Error("record 2 should be deleted")
}
// Recall record 2
area.GoTo(2)
area.Recall()
area.Flush()
area.GoTo(2)
if area.Deleted() {
t.Error("record 2 should be recalled")
}
// Pack (removes record 4 which is still deleted)
area.Pack()
rc, _ := area.RecCount()
if rc != 4 {
t.Errorf("after pack reccount = %d, want 4", rc)
}
// Verify remaining records: 1, 2, 3, 5
area.GoTo(4)
v, _ := area.GetValue(0)
if v.AsNumInt() != 5 {
t.Errorf("after pack rec 4 ID = %d, want 5", v.AsNumInt())
}
area.Close()
}
func TestZap(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, _ := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{{Name: "ID", Type: 'N', Len: 5}},
})
for i := 1; i <= 10; i++ {
area.Append()
area.PutValue(0, hbrt.MakeInt(i))
area.Flush()
}
rc, _ := area.RecCount()
if rc != 10 {
t.Fatalf("before zap: %d", rc)
}
area.Zap()
rc, _ = area.RecCount()
if rc != 0 {
t.Errorf("after zap: %d, want 0", rc)
}
if !area.EOF() {
t.Error("after zap should be EOF")
}
area.Close()
}
func TestLogicalField(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, _ := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "FLAG", Type: 'L', Len: 1},
},
})
area.Append()
area.PutValue(0, hbrt.MakeBool(true))
area.Flush()
area.Append()
area.PutValue(0, hbrt.MakeBool(false))
area.Flush()
area.Close()
area2, _ := drv.Open(hbrdd.OpenParams{Path: path})
defer area2.Close()
area2.GoTo(1)
v, _ := area2.GetValue(0)
if !v.AsBool() {
t.Error("rec 1 should be TRUE")
}
area2.GoTo(2)
v, _ = area2.GetValue(0)
if v.AsBool() {
t.Error("rec 2 should be FALSE")
}
}
func TestFieldTypes(t *testing.T) {
dir := tempDir(t)
path := filepath.Join(dir, "test")
drv := &DBFDriver{}
area, _ := drv.Create(hbrdd.CreateParams{
Path: path,
Fields: []hbrdd.FieldInfo{
{Name: "CHAR", Type: 'C', Len: 10},
{Name: "NUM", Type: 'N', Len: 10, Dec: 2},
{Name: "LOGIC", Type: 'L', Len: 1},
{Name: "DATE", Type: 'D', Len: 8},
},
})
area.Append()
area.PutValue(0, hbrt.MakeString("Hello"))
area.PutValue(1, hbrt.MakeDouble(123.45, 10, 2))
area.PutValue(2, hbrt.MakeBool(true))
area.PutValue(3, hbrt.MakeDate(2461033)) // 2026-03-27
area.Flush()
area.Close()
area2, _ := drv.Open(hbrdd.OpenParams{Path: path})
defer area2.Close()
area2.GoTo(1)
v0, _ := area2.GetValue(0)
if v0.AsString()[:5] != "Hello" {
t.Errorf("CHAR = %q", v0.AsString())
}
v1, _ := area2.GetValue(1)
if v1.AsDouble() != 123.45 {
t.Errorf("NUM = %f, want 123.45", v1.AsDouble())
}
v2, _ := area2.GetValue(2)
if !v2.AsBool() {
t.Error("LOGIC should be TRUE")
}
v3, _ := area2.GetValue(3)
if v3.AsJulian() != 2461033 {
t.Errorf("DATE julian = %d, want 2461033", v3.AsJulian())
}
}