- 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>
371 lines
7.0 KiB
Go
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())
|
|
}
|
|
}
|