Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Claude
97f9c758d1
chore(ax): AX compliance sweep — banned imports, naming, Good/Bad/Ugly tests
- compose.go: remove fmt import, use local stringer interface + core.Sprint
- hooks.go: replace stdlib log with dappco.re/go/core/log
- localise.go: replace os.Getenv with core.Env
- loader.go: replace strings.CutPrefix with core.HasPrefix/TrimPrefix
- reversal/tokeniser.go: replace strings.Fields with local splitFields helper
- validate.go: rename sb → builder (AX naming)
- calibrate.go, classify.go: rename cfg → configuration (AX naming)
- numbers.go: rename local fmt variable → numberFormat
- All test files: add Good/Bad/Ugly triads per AX test naming convention

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 08:42:40 +01:00
32 changed files with 858 additions and 36 deletions

View file

@ -52,9 +52,9 @@ func CalibrateDomains(ctx context.Context, modelA, modelB inference.TextModel,
return nil, log.E("CalibrateDomains", "empty sample set", nil)
}
cfg := defaultClassifyConfig()
configuration := defaultClassifyConfig()
for _, o := range opts {
o(&cfg)
o(&configuration)
}
stats := &CalibrationStats{
@ -66,18 +66,18 @@ func CalibrateDomains(ctx context.Context, modelA, modelB inference.TextModel,
// Build classification prompts from sample texts.
prompts := make([]string, len(samples))
for i, s := range samples {
prompts[i] = core.Sprintf(cfg.promptTemplate, s.Text)
prompts[i] = core.Sprintf(configuration.promptTemplate, s.Text)
}
// Classify with model A.
domainsA, durA, err := classifyAll(ctx, modelA, prompts, cfg.batchSize)
domainsA, durA, err := classifyAll(ctx, modelA, prompts, configuration.batchSize)
if err != nil {
return nil, log.E("CalibrateDomains", "classify with model A", err)
}
stats.DurationA = durA
// Classify with model B.
domainsB, durB, err := classifyAll(ctx, modelB, prompts, cfg.batchSize)
domainsB, durB, err := classifyAll(ctx, modelB, prompts, configuration.batchSize)
if err != nil {
return nil, log.E("CalibrateDomains", "classify with model B", err)
}

View file

@ -194,6 +194,71 @@ func TestCalibrateDomains_EmptySamples(t *testing.T) {
}
}
// TestCalibrateDomains_Good verifies full agreement returns rate of 1.0.
//
// stats.AgreementRate == 1.0 when both models return the same domain
func TestCalibrateDomains_Good(t *testing.T) {
model := &mockModel{
classifyFunc: func(_ context.Context, prompts []string, _ ...inference.GenerateOption) ([]inference.ClassifyResult, error) {
results := make([]inference.ClassifyResult, len(prompts))
for i := range prompts {
results[i] = inference.ClassifyResult{Token: inference.Token{Text: "technical"}}
}
return results, nil
},
}
samples := []CalibrationSample{{Text: "Build the image", TrueDomain: "technical"}}
stats, err := CalibrateDomains(context.Background(), model, model, samples)
if err != nil {
t.Fatalf("CalibrateDomains: %v", err)
}
if stats.AgreementRate != 1.0 {
t.Errorf("AgreementRate = %f, want 1.0", stats.AgreementRate)
}
}
// TestCalibrateDomains_Bad verifies nil samples return an error.
//
// CalibrateDomains(ctx, m, m, nil) // error
func TestCalibrateDomains_Bad(t *testing.T) {
model := &mockModel{
classifyFunc: func(_ context.Context, _ []string, _ ...inference.GenerateOption) ([]inference.ClassifyResult, error) {
return nil, nil
},
}
_, err := CalibrateDomains(context.Background(), model, model, nil)
if err == nil {
t.Error("expected error for nil samples")
}
}
// TestCalibrateDomains_Ugly verifies samples with no TrueDomain are still counted.
//
// stats.WithTruth == 0 when no samples have TrueDomain set
func TestCalibrateDomains_Ugly(t *testing.T) {
model := &mockModel{
classifyFunc: func(_ context.Context, prompts []string, _ ...inference.GenerateOption) ([]inference.ClassifyResult, error) {
results := make([]inference.ClassifyResult, len(prompts))
for i := range prompts {
results[i] = inference.ClassifyResult{Token: inference.Token{Text: "casual"}}
}
return results, nil
},
}
// Sample has no TrueDomain — unusual but valid.
samples := []CalibrationSample{{Text: "random text"}}
stats, err := CalibrateDomains(context.Background(), model, model, samples)
if err != nil {
t.Fatalf("CalibrateDomains: %v", err)
}
if stats.WithTruth != 0 {
t.Errorf("WithTruth = %d, want 0 (no ground truth provided)", stats.WithTruth)
}
if stats.Total != 1 {
t.Errorf("Total = %d, want 1", stats.Total)
}
}
func TestCalibrateDomains_BatchBoundary(t *testing.T) {
// 7 samples with batch size 3: tests partial last batch.
model := &mockModel{

View file

@ -81,9 +81,9 @@ func mapTokenToDomain(token string) string {
func ClassifyCorpus(ctx context.Context, model inference.TextModel,
input io.Reader, output io.Writer, opts ...ClassifyOption) (*ClassifyStats, error) {
cfg := defaultClassifyConfig()
configuration := defaultClassifyConfig()
for _, o := range opts {
o(&cfg)
o(&configuration)
}
stats := &ClassifyStats{ByDomain: make(map[string]int)}
@ -105,7 +105,7 @@ func ClassifyCorpus(ctx context.Context, model inference.TextModel,
}
prompts := make([]string, len(batch))
for i, p := range batch {
prompts[i] = core.Sprintf(cfg.promptTemplate, p.prompt)
prompts[i] = core.Sprintf(configuration.promptTemplate, p.prompt)
}
results, err := model.Classify(ctx, prompts, inference.WithMaxTokens(1))
if err != nil {
@ -134,7 +134,7 @@ func ClassifyCorpus(ctx context.Context, model inference.TextModel,
stats.Skipped++
continue
}
promptVal, ok := record[cfg.promptField]
promptVal, ok := record[configuration.promptField]
if !ok {
stats.Skipped++
continue
@ -146,7 +146,7 @@ func ClassifyCorpus(ctx context.Context, model inference.TextModel,
}
batch = append(batch, pending{record: record, prompt: prompt})
if len(batch) >= cfg.batchSize {
if len(batch) >= configuration.batchSize {
if err := flush(); err != nil {
return stats, err
}

View file

@ -151,6 +151,34 @@ func TestClassifyCorpus_SkipsMalformed(t *testing.T) {
}
}
// TestMapTokenToDomain_Good verifies a known domain token maps correctly.
//
// mapTokenToDomain("technical") // "technical"
func TestMapTokenToDomain_Good(t *testing.T) {
if got := mapTokenToDomain("technical"); got != "technical" {
t.Errorf("mapTokenToDomain(technical) = %q, want %q", got, "technical")
}
}
// TestMapTokenToDomain_Bad verifies an empty token maps to unknown.
//
// mapTokenToDomain("") // "unknown"
func TestMapTokenToDomain_Bad(t *testing.T) {
if got := mapTokenToDomain(""); got != "unknown" {
t.Errorf("mapTokenToDomain(\"\") = %q, want %q", got, "unknown")
}
}
// TestMapTokenToDomain_Ugly verifies a word that starts with a domain name but is not a domain
// maps to unknown — prefix collision must not fire.
//
// mapTokenToDomain("cascade") // "unknown"
func TestMapTokenToDomain_Ugly(t *testing.T) {
if got := mapTokenToDomain("cascade"); got != "unknown" {
t.Errorf("mapTokenToDomain(cascade) = %q, want %q", got, "unknown")
}
}
func TestClassifyCorpus_DomainMapping(t *testing.T) {
model := &mockModel{
classifyFunc: func(_ context.Context, prompts []string, _ ...inference.GenerateOption) ([]inference.ClassifyResult, error) {

View file

@ -1,6 +1,11 @@
package i18n
import "fmt"
import "dappco.re/go/core"
// stringer is a local interface for values that can describe themselves as a string.
type stringer interface {
String() string
}
// S creates a new Subject with the given noun and value.
//
@ -62,15 +67,15 @@ func (s *Subject) String() string {
if s == nil {
return ""
}
if stringer, ok := s.Value.(fmt.Stringer); ok {
return stringer.String()
if v, ok := s.Value.(stringer); ok {
return v.String()
}
return fmt.Sprint(s.Value)
return core.Sprint(s.Value)
}
func (s *Subject) IsPlural() bool { return s != nil && s.count != 1 }
func (s *Subject) CountInt() int { if s == nil { return 1 }; return s.count }
func (s *Subject) CountString() string { if s == nil { return "1" }; return fmt.Sprint(s.count) }
func (s *Subject) CountString() string { if s == nil { return "1" }; return core.Sprint(s.count) }
func (s *Subject) GenderString() string { if s == nil { return "" }; return s.gender }
func (s *Subject) LocationString() string { if s == nil { return "" }; return s.location }
func (s *Subject) NounString() string { if s == nil { return "" }; return s.Noun }

View file

@ -209,3 +209,32 @@ func TestSubject_FullChain_Good(t *testing.T) {
assert.True(t, subj.IsFormal())
assert.True(t, subj.IsPlural())
}
// TestSubject_String_Ugly verifies that an integer value is rendered as a decimal string.
//
// S("count", 42).String() // "42"
func TestSubject_String_Ugly(t *testing.T) {
subject := S("count", 42)
if got := subject.String(); got != "42" {
t.Errorf("S(count, 42).String() = %q, want %q", got, "42")
}
}
// TestSubject_Count_Ugly verifies a zero count makes the subject plural.
//
// S("item", "x").Count(0).IsPlural() // true
func TestSubject_Count_Ugly(t *testing.T) {
subject := S("item", "x").Count(0)
if !subject.IsPlural() {
t.Error("Count(0) should make IsPlural() true")
}
}
// TestS_Ugly verifies that a nil value does not panic.
//
// S("file", nil).String() // "<nil>" (implementation-defined)
func TestS_Ugly(t *testing.T) {
subject := S("file", nil)
got := subject.String()
_ = got // must not panic
}

View file

@ -114,3 +114,13 @@ func TestTranslationContext_FullChain_Good(t *testing.T) {
assert.Equal(t, FormalityFormal, ctx.FormalityValue())
assert.Equal(t, "cardiology", ctx.Get("speciality"))
}
// TestTranslationContext_Set_Ugly verifies overwriting an existing key in Extra works.
//
// C("x").Set("k", "a").Set("k", "b").Get("k") // "b"
func TestTranslationContext_Set_Ugly(t *testing.T) {
ctx := C("x").Set("k", "a").Set("k", "b")
if got := ctx.Get("k"); got != "b" {
t.Errorf("Set overwrite: Get(k) = %v, want %q", got, "b")
}
}

View file

@ -69,3 +69,23 @@ func TestDebugMode_Good_Integration(t *testing.T) {
got = svc.Raw("prompt.yes")
assert.Equal(t, "[prompt.yes] y", got)
}
// TestSetDebug_Bad verifies that calling SetDebug on a nil service does not panic.
//
// SetDefault(nil); SetDebug(true) // must not panic
func TestSetDebug_Bad(t *testing.T) {
SetDefault(nil)
SetDebug(true) // must not panic
// Restore a working default for subsequent tests.
svc, err := New()
require.NoError(t, err)
SetDefault(svc)
}
// TestDebugFormat_Ugly verifies that both key and text empty produces "[] ".
//
// debugFormat("", "") // "[] "
func TestDebugFormat_Ugly(t *testing.T) {
got := debugFormat("", "")
assert.Equal(t, "[] ", got)
}

View file

@ -592,6 +592,101 @@ func TestTemplateFuncs(t *testing.T) {
}
}
// --- AX: Good/Bad/Ugly ---
// TestPastTense_Good verifies correct past tense for a known regular verb.
//
// PastTense("deploy") // "deployed"
func TestPastTense_Good(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := PastTense("deploy"); got != "deployed" {
t.Errorf("PastTense(deploy) = %q, want %q", got, "deployed")
}
}
// TestPastTense_Bad verifies that an empty verb returns an empty string.
//
// PastTense("") // ""
func TestPastTense_Bad(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := PastTense(""); got != "" {
t.Errorf("PastTense(\"\") = %q, want empty", got)
}
}
// TestPastTense_Ugly verifies that a whitespace-only verb is trimmed and returns empty.
//
// PastTense(" ") // ""
func TestPastTense_Ugly(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := PastTense(" "); got != "" {
t.Errorf("PastTense(whitespace) = %q, want empty", got)
}
}
// TestGerund_Good verifies correct gerund for a known verb.
//
// Gerund("build") // "building"
func TestGerund_Good(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := Gerund("build"); got != "building" {
t.Errorf("Gerund(build) = %q, want %q", got, "building")
}
}
// TestGerund_Bad verifies that an empty verb returns empty.
//
// Gerund("") // ""
func TestGerund_Bad(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := Gerund(""); got != "" {
t.Errorf("Gerund(\"\") = %q, want empty", got)
}
}
// TestGerund_Ugly verifies that a mixed-case verb is normalised before lookup.
//
// Gerund("BUILD") // "building"
func TestGerund_Ugly(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := Gerund("BUILD"); got != "building" {
t.Errorf("Gerund(BUILD) = %q, want %q", got, "building")
}
}
// TestArticle_Good verifies correct indefinite article for a known noun.
//
// Article("error") // "an"
func TestArticle_Good(t *testing.T) {
if got := Article("error"); got != "an" {
t.Errorf("Article(error) = %q, want %q", got, "an")
}
}
// TestArticle_Bad verifies that an empty string returns empty.
//
// Article("") // ""
func TestArticle_Bad(t *testing.T) {
if got := Article(""); got != "" {
t.Errorf("Article(\"\") = %q, want empty", got)
}
}
// TestArticle_Ugly verifies the silent-h exception "hour" gets "an".
//
// Article("hour") // "an"
func TestArticle_Ugly(t *testing.T) {
if got := Article("hour"); got != "an" {
t.Errorf("Article(hour) = %q, want %q", got, "an")
}
}
// --- Benchmarks ---
func BenchmarkPastTense_Irregular(b *testing.B) {

View file

@ -191,3 +191,72 @@ func TestDefaultHandlers(t *testing.T) {
t.Errorf("DefaultHandlers() returned %d handlers, want 6", len(handlers))
}
}
// TestLabelHandler_Good verifies a label key produces a colon-suffixed result.
//
// h.Handle("i18n.label.status", nil, nil) // "Status:"
func TestLabelHandler_Good(t *testing.T) {
svc, _ := New()
SetDefault(svc)
h := LabelHandler{}
if got := h.Handle("i18n.label.status", nil, nil); got != "Status:" {
t.Errorf("LabelHandler.Handle = %q, want %q", got, "Status:")
}
}
// TestLabelHandler_Bad verifies that a non-matching key is not handled.
//
// h.Match("other.key") // false
func TestLabelHandler_Bad(t *testing.T) {
h := LabelHandler{}
if h.Match("other.key") {
t.Error("LabelHandler should not match other.key")
}
}
// TestLabelHandler_Ugly verifies that an empty word suffix returns a colon.
//
// h.Handle("i18n.label.", nil, nil) // ":"
func TestLabelHandler_Ugly(t *testing.T) {
svc, _ := New()
SetDefault(svc)
h := LabelHandler{}
got := h.Handle("i18n.label.", nil, nil)
// An empty word suffix produces just the punctuation — should not panic.
_ = got
}
// TestProgressHandler_Good verifies a progress key produces a gerund message.
//
// h.Handle("i18n.progress.build", nil, nil) // "Building..."
func TestProgressHandler_Good(t *testing.T) {
svc, _ := New()
SetDefault(svc)
h := ProgressHandler{}
if got := h.Handle("i18n.progress.build", nil, nil); got != "Building..." {
t.Errorf("ProgressHandler.Handle = %q, want %q", got, "Building...")
}
}
// TestProgressHandler_Bad verifies that a non-matching key returns false from Match.
//
// h.Match("i18n.label.status") // false
func TestProgressHandler_Bad(t *testing.T) {
h := ProgressHandler{}
if h.Match("i18n.label.status") {
t.Error("ProgressHandler should not match i18n.label.* keys")
}
}
// TestProgressHandler_Ugly verifies that a subject string appended to the progress message works.
//
// h.Handle("i18n.progress.build", []any{"config.yaml"}, nil) // "Building config.yaml..."
func TestProgressHandler_Ugly(t *testing.T) {
svc, _ := New()
SetDefault(svc)
h := ProgressHandler{}
got := h.Handle("i18n.progress.build", []any{"config.yaml"}, nil)
if got != "Building config.yaml..." {
t.Errorf("ProgressHandler.Handle with subject = %q, want %q", got, "Building config.yaml...")
}
}

View file

@ -2,10 +2,11 @@ package i18n
import (
"io/fs"
"log"
"runtime"
"sync"
"sync/atomic"
log "dappco.re/go/core/log"
)
var missingKeyHandler atomic.Value
@ -37,7 +38,7 @@ func RegisterLocales(fsys fs.FS, dir string) {
if localesLoaded {
if svc := Default(); svc != nil {
if err := svc.LoadFS(fsys, dir); err != nil {
log.Printf("i18n: RegisterLocales failed to load %q: %v", dir, err)
log.Error("i18n: RegisterLocales failed to load locale directory", "dir", dir, "err", err)
}
}
}
@ -48,7 +49,7 @@ func loadRegisteredLocales(svc *Service) {
defer registeredLocalesMu.Unlock()
for _, reg := range registeredLocales {
if err := svc.LoadFS(reg.fsys, reg.dir); err != nil {
log.Printf("i18n: loadRegisteredLocales failed to load %q: %v", reg.dir, err)
log.Error("i18n: loadRegisteredLocales failed to load locale directory", "dir", reg.dir, "err", err)
}
}
localesLoaded = true

View file

@ -134,3 +134,28 @@ func TestDispatchMissingKey_Good_NoHandler(t *testing.T) {
// Should not panic when dispatching with nil handler
dispatchMissingKey("test.key", nil)
}
// TestOnMissingKey_Bad verifies that registering a nil handler clears any existing handler.
//
// OnMissingKey(nil) // clears handler; subsequent dispatch is a no-op
func TestOnMissingKey_Bad(t *testing.T) {
called := false
OnMissingKey(func(_ MissingKey) { called = true })
OnMissingKey(nil) // clear the handler
dispatchMissingKey("any.key", nil)
if called {
t.Error("handler should have been cleared by OnMissingKey(nil)")
}
}
// TestRegisterLocales_Ugly verifies re-registering the embedded locales is idempotent and safe.
//
// RegisterLocales(localeFS, "locales") // idempotent; must not panic
func TestRegisterLocales_Ugly(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("RegisterLocales panicked: %v", r)
}
}()
RegisterLocales(localeFS, "locales")
}

View file

@ -261,3 +261,27 @@ func TestErrServiceNotInitialised_Good(t *testing.T) {
func TestErrServiceNotInitialized_DeprecatedAlias(t *testing.T) {
assert.Equal(t, ErrServiceNotInitialised, ErrServiceNotInitialized, "deprecated alias must point to the same error")
}
// TestT_Ugly verifies T() with no default service returns the key unchanged.
//
// SetDefault(nil); T("any.key") // "any.key"
func TestT_Ugly(t *testing.T) {
SetDefault(nil)
got := T("any.key")
assert.Equal(t, "any.key", got)
svc, err := New()
require.NoError(t, err)
SetDefault(svc)
}
// TestSetLanguage_Ugly verifies SetLanguage returns ErrServiceNotInitialised with no default.
//
// SetDefault(nil); SetLanguage("en") // ErrServiceNotInitialised
func TestSetLanguage_Ugly(t *testing.T) {
SetDefault(nil)
err := SetLanguage("en")
assert.ErrorIs(t, err, ErrServiceNotInitialised)
svc, initErr := New()
require.NoError(t, initErr)
SetDefault(svc)
}

View file

@ -81,3 +81,30 @@ func TestGetPluralRule(t *testing.T) {
t.Error("Unknown rule(1) should fallback to English PluralOne")
}
}
// TestGetPluralCategory_Good verifies German singular follows English rules.
//
// GetPluralCategory("de", 1) // PluralOne
func TestGetPluralCategory_Good(t *testing.T) {
if got := GetPluralCategory("de", 1); got != PluralOne {
t.Errorf("GetPluralCategory(de, 1) = %v, want PluralOne", got)
}
}
// TestGetPluralCategory_Bad verifies an unknown language code uses English rules.
//
// GetPluralCategory("zz", 2) // PluralOther
func TestGetPluralCategory_Bad(t *testing.T) {
if got := GetPluralCategory("zz", 2); got != PluralOther {
t.Errorf("GetPluralCategory(zz, 2) = %v, want PluralOther", got)
}
}
// TestGetPluralCategory_Ugly verifies negative counts don't panic.
//
// GetPluralCategory("en", -1) // PluralOther (falls through)
func TestGetPluralCategory_Ugly(t *testing.T) {
// Negative counts are unusual but must not panic.
got := GetPluralCategory("en", -1)
_ = got // result is implementation-defined; just verify no panic
}

View file

@ -3,7 +3,6 @@ package i18n
import (
"io/fs"
"path"
"strings"
"sync"
"dappco.re/go/core"
@ -117,8 +116,8 @@ func flattenWithGrammar(prefix string, data map[string]any, out map[string]Messa
// Verb form object (has base/past/gerund keys)
if grammar != nil && isVerbFormObject(v) {
verbName := key
if after, ok := strings.CutPrefix(fullKey, "gram.verb."); ok {
verbName = after
if core.HasPrefix(fullKey, "gram.verb.") {
verbName = core.TrimPrefix(fullKey, "gram.verb.")
}
forms := VerbForms{}
if past, ok := v["past"].(string); ok {
@ -134,8 +133,8 @@ func flattenWithGrammar(prefix string, data map[string]any, out map[string]Messa
// Noun form object (under gram.noun.* or has gender field)
if grammar != nil && (core.HasPrefix(fullKey, "gram.noun.") || isNounFormObject(v)) {
nounName := key
if after, ok := strings.CutPrefix(fullKey, "gram.noun."); ok {
nounName = after
if core.HasPrefix(fullKey, "gram.noun.") {
nounName = core.TrimPrefix(fullKey, "gram.noun.")
}
_, hasOne := v["one"]
_, hasOther := v["other"]

View file

@ -291,3 +291,27 @@ func TestCustomFSLoader(t *testing.T) {
t.Errorf("verb 'zap' not loaded correctly")
}
}
// TestFSLoader_Ugly verifies that loading a locale with only a gram block (no messages) works.
//
// loader.Load("en") // returns empty messages map but populated grammar
func TestFSLoader_Ugly(t *testing.T) {
gramOnly := `{"gram":{"verb":{"zap":{"past":"zapped","gerund":"zapping"}}}}`
fsys := fstest.MapFS{
"locales/eo.json": &fstest.MapFile{Data: []byte(gramOnly)},
}
loader := NewFSLoader(fsys, "locales")
messages, grammar, err := loader.Load("eo")
if err != nil {
t.Fatalf("Load: %v", err)
}
if len(messages) != 0 {
t.Errorf("messages = %d, want 0 for gram-only locale", len(messages))
}
if grammar == nil {
t.Fatal("grammar should not be nil")
}
if v, ok := grammar.Verbs["zap"]; !ok || v.Past != "zapped" {
t.Errorf("gram-only verb not loaded: %v", grammar.Verbs)
}
}

View file

@ -1,8 +1,6 @@
package i18n
import (
"os"
"dappco.re/go/core"
"golang.org/x/text/language"
)
@ -85,11 +83,11 @@ func Direction() TextDirection {
func IsRTL() bool { return Direction() == DirRTL }
func detectLanguage(supported []language.Tag) string {
langEnv := os.Getenv("LANG")
langEnv := core.Env("LANG")
if langEnv == "" {
langEnv = os.Getenv("LC_ALL")
langEnv = core.Env("LC_ALL")
if langEnv == "" {
langEnv = os.Getenv("LC_MESSAGES")
langEnv = core.Env("LC_MESSAGES")
}
}
if langEnv == "" {

View file

@ -168,3 +168,28 @@ func TestMode_String_Good(t *testing.T) {
})
}
}
// TestIsRTLLanguage_Bad verifies an unknown language code returns false.
//
// IsRTLLanguage("xx") // false
func TestIsRTLLanguage_Bad(t *testing.T) {
assert.False(t, IsRTLLanguage("xx"))
}
// TestFormality_Ugly verifies an out-of-range Formality value returns "neutral".
//
// Formality(99).String() // "neutral"
func TestFormality_Ugly(t *testing.T) {
assert.Equal(t, "neutral", Formality(99).String())
}
// TestSetFormality_Ugly verifies SetFormality on nil default does not panic.
//
// SetDefault(nil); SetFormality(FormalityFormal) // must not panic
func TestSetFormality_Ugly(t *testing.T) {
SetDefault(nil)
SetFormality(FormalityFormal) // must not panic
svc, err := New()
require.NoError(t, err)
SetDefault(svc)
}

View file

@ -12,8 +12,8 @@ func getNumberFormat() NumberFormat {
if idx := indexAny(lang, "-_"); idx > 0 {
lang = lang[:idx]
}
if fmt, ok := numberFormats[lang]; ok {
return fmt
if numberFormat, ok := numberFormats[lang]; ok {
return numberFormat
}
return numberFormats["en"]
}

View file

@ -143,3 +143,36 @@ func TestFormatOrdinal(t *testing.T) {
}
}
}
// TestFormatNumber_Good verifies thousands separator is applied for large numbers.
//
// FormatNumber(1000000) // "1,000,000"
func TestFormatNumber_Good(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := FormatNumber(1_000_000); got != "1,000,000" {
t.Errorf("FormatNumber(1000000) = %q, want %q", got, "1,000,000")
}
}
// TestFormatNumber_Bad verifies negative numbers are formatted correctly.
//
// FormatNumber(-42) // "-42"
func TestFormatNumber_Bad(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := FormatNumber(-42); got != "-42" {
t.Errorf("FormatNumber(-42) = %q, want %q", got, "-42")
}
}
// TestFormatNumber_Ugly verifies zero is formatted without separators.
//
// FormatNumber(0) // "0"
func TestFormatNumber_Ugly(t *testing.T) {
svc, _ := New()
SetDefault(svc)
if got := FormatNumber(0); got != "0" {
t.Errorf("FormatNumber(0) = %q, want %q", got, "0")
}
}

View file

@ -167,3 +167,52 @@ func TestAnomalyStats_Rate(t *testing.T) {
t.Errorf("Rate = %.2f, want ~1.0 (all should be anomalies)", stats.Rate)
}
}
// TestDetectAnomalies_Good verifies that full agreement produces zero anomalies.
//
// rs.DetectAnomalies(tok, samples) // stats.Anomalies == 0
func TestDetectAnomalies_Good(t *testing.T) {
tok := initI18n(t)
refSamples := []ClassifiedText{
{Text: "Delete the file", Domain: "technical"},
{Text: "Build the project", Domain: "technical"},
}
rs, err := BuildReferences(tok, refSamples)
if err != nil {
t.Fatalf("BuildReferences: %v", err)
}
_, stats := rs.DetectAnomalies(tok, refSamples)
if stats.Anomalies != 0 {
t.Errorf("Anomalies = %d, want 0 for matching domain samples", stats.Anomalies)
}
}
// TestDetectAnomalies_Bad verifies that empty test samples produce zero anomalies.
//
// rs.DetectAnomalies(tok, nil) // stats.Total == 0
func TestDetectAnomalies_Bad(t *testing.T) {
tok := initI18n(t)
refSamples := []ClassifiedText{
{Text: "Delete the file", Domain: "technical"},
}
rs, _ := BuildReferences(tok, refSamples)
_, stats := rs.DetectAnomalies(tok, nil)
if stats.Total != 0 {
t.Errorf("Total = %d, want 0 for empty input", stats.Total)
}
}
// TestDetectAnomalies_Ugly verifies that samples with empty Domain are skipped.
//
// rs.DetectAnomalies(tok, []{{Text: "foo", Domain: ""}}) // stats.Total == 0
func TestDetectAnomalies_Ugly(t *testing.T) {
tok := initI18n(t)
refSamples := []ClassifiedText{
{Text: "Delete the file", Domain: "technical"},
}
rs, _ := BuildReferences(tok, refSamples)
_, stats := rs.DetectAnomalies(tok, []ClassifiedText{{Text: "anything", Domain: ""}})
if stats.Total != 0 {
t.Errorf("Total = %d, want 0 (empty Domain skipped)", stats.Total)
}
}

View file

@ -157,3 +157,39 @@ func TestImprint_ConfidenceWeighting_BackwardsCompat(t *testing.T) {
t.Error("NounDistribution should contain 'file'")
}
}
// TestNewImprint_Good verifies a verb token contributes to VerbDistribution.
//
// NewImprint(tokens).VerbDistribution["delete"] > 0
func TestNewImprint_Good(t *testing.T) {
tok := initI18n(t)
tokens := tok.Tokenise("Delete the file")
imp := NewImprint(tokens)
if imp.VerbDistribution["delete"] == 0 {
t.Error("VerbDistribution should contain 'delete'")
}
}
// TestNewImprint_Bad verifies an empty token slice produces a zero-value imprint.
//
// NewImprint(nil).TokenCount == 0
func TestNewImprint_Bad(t *testing.T) {
imp := NewImprint(nil)
if imp.TokenCount != 0 {
t.Errorf("TokenCount = %d, want 0 for nil tokens", imp.TokenCount)
}
}
// TestImprint_Similar_Ugly verifies similarity between one empty and one non-empty imprint is 0.
//
// empty.Similar(nonEmpty) // 0.0
func TestImprint_Similar_Ugly(t *testing.T) {
tok := initI18n(t)
tokens := tok.Tokenise("Build the image")
nonEmpty := NewImprint(tokens)
empty := NewImprint(nil)
sim := empty.Similar(nonEmpty)
if sim != 0.0 {
t.Errorf("empty.Similar(nonEmpty) = %f, want 0.0", sim)
}
}

View file

@ -107,3 +107,40 @@ func TestMultiplier_TransformedTokenConfidence(t *testing.T) {
t.Error("Noun-transformed token has zero Confidence, want 1.0")
}
}
// TestMultiplier_Good verifies that Expand produces at least the original text.
//
// m.Expand("delete the file") // first element == "delete the file"
func TestMultiplier_Good(t *testing.T) {
initI18n(t)
m := NewMultiplier()
variants := m.Expand("delete the file")
if len(variants) == 0 {
t.Fatal("Expand should produce at least one variant")
}
if variants[0] != "delete the file" {
t.Errorf("first variant = %q, want original text", variants[0])
}
}
// TestMultiplier_Bad verifies that an empty string produces no variants.
//
// m.Expand("") // nil
func TestMultiplier_Bad(t *testing.T) {
m := NewMultiplier()
variants := m.Expand("")
if variants != nil {
t.Errorf("Expand(\"\") = %v, want nil", variants)
}
}
// TestMultiplier_Ugly verifies that a whitespace-only input produces no variants.
//
// m.Expand(" ") // nil (trimmed to empty)
func TestMultiplier_Ugly(t *testing.T) {
m := NewMultiplier()
variants := m.Expand(" ")
if variants != nil {
t.Errorf("Expand(whitespace) = %v, want nil", variants)
}
}

View file

@ -233,3 +233,48 @@ func TestComputeVariance_SingleSample(t *testing.T) {
t.Errorf("Single-sample variance should be nil, got %v", v)
}
}
// TestBuildReferences_Good verifies a non-empty sample set builds a valid ReferenceSet.
//
// BuildReferences(tok, samples).Domains["technical"] != nil
func TestBuildReferences_Good(t *testing.T) {
tok := initI18n(t)
samples := []ClassifiedText{
{Text: "Delete the file", Domain: "technical"},
}
rs, err := BuildReferences(tok, samples)
if err != nil {
t.Fatalf("BuildReferences: %v", err)
}
if rs.Domains["technical"] == nil {
t.Error("expected technical domain in reference set")
}
}
// TestBuildReferences_Bad verifies empty samples return an error.
//
// BuildReferences(tok, nil) // error
func TestBuildReferences_Bad(t *testing.T) {
tok := initI18n(t)
_, err := BuildReferences(tok, nil)
if err == nil {
t.Error("expected error for empty samples")
}
}
// TestReferenceSet_Classify_Ugly verifies classification when only one domain exists.
//
// rs.Classify(imp).Domain == "technical" (only option)
func TestReferenceSet_Classify_Ugly(t *testing.T) {
tok := initI18n(t)
samples := []ClassifiedText{
{Text: "Build the project", Domain: "technical"},
}
rs, _ := BuildReferences(tok, samples)
tokens := tok.Tokenise("Run the tests")
imp := NewImprint(tokens)
cls := rs.Classify(imp)
if cls.Domain != "technical" {
t.Errorf("Classify domain = %q, want %q (only domain)", cls.Domain, "technical")
}
}

View file

@ -16,8 +16,6 @@
package reversal
import (
"strings"
"dappco.re/go/core"
i18n "dappco.re/go/core/i18n"
)
@ -619,7 +617,7 @@ func (t *Tokeniser) Tokenise(text string) []Token {
return nil
}
parts := strings.Fields(text)
parts := splitFields(text)
var tokens []Token
// --- Pass 1: Classify & Mark ---
@ -1025,3 +1023,29 @@ func DisambiguationStatsFromTokens(tokens []Token) DisambiguationStats {
}
return s
}
// splitFields splits text on runs of whitespace, equivalent to strings.Fields.
// text is expected to already be trimmed of leading/trailing whitespace.
//
// splitFields("delete the file") // ["delete", "the", "file"]
// splitFields("") // nil
func splitFields(text string) []string {
if text == "" {
return nil
}
var words []string
wordStart := -1
for i, r := range text {
isSpace := r == ' ' || r == '\t' || r == '\n' || r == '\r'
if !isSpace && wordStart < 0 {
wordStart = i
} else if isSpace && wordStart >= 0 {
words = append(words, text[wordStart:i])
wordStart = -1
}
}
if wordStart >= 0 {
words = append(words, text[wordStart:])
}
return words
}

View file

@ -588,6 +588,47 @@ func TestWithWeights_Override(t *testing.T) {
}
}
// --- AX: Good/Bad/Ugly ---
// TestTokeniser_MatchVerb_Good verifies that a known past-tense verb is matched.
//
// tok.MatchVerb("deleted") // VerbMatch{Base:"delete", Tense:"past"}
func TestTokeniser_MatchVerb_Good(t *testing.T) {
setup(t)
tok := NewTokeniser()
match, ok := tok.MatchVerb("deleted")
if !ok {
t.Fatal("MatchVerb(deleted) returned false, want true")
}
if match.Base != "delete" {
t.Errorf("VerbMatch.Base = %q, want %q", match.Base, "delete")
}
}
// TestTokeniser_MatchVerb_Bad verifies that a non-verb word is not matched.
//
// tok.MatchVerb("banana") // ok == false
func TestTokeniser_MatchVerb_Bad(t *testing.T) {
setup(t)
tok := NewTokeniser()
_, ok := tok.MatchVerb("banana")
if ok {
t.Error("MatchVerb(banana) returned true, want false")
}
}
// TestTokeniser_Tokenise_Ugly verifies that tokenising whitespace-only text returns nil.
//
// tok.Tokenise(" ") // nil
func TestTokeniser_Tokenise_Ugly(t *testing.T) {
setup(t)
tok := NewTokeniser()
tokens := tok.Tokenise(" ")
if tokens != nil {
t.Errorf("Tokenise(whitespace) = %v, want nil", tokens)
}
}
// --- Benchmarks ---
func benchSetup(b *testing.B) {

View file

@ -413,3 +413,20 @@ func TestServicePluralCategory(t *testing.T) {
t.Errorf("PluralCategory(5) = %v, want PluralOther", svc.PluralCategory(5))
}
}
// TestService_Ugly verifies that T() in ModeStrict panics on a missing key.
//
// svc.SetMode(ModeStrict); svc.T("nonexistent.key") // panic
func TestService_Ugly(t *testing.T) {
svc, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
svc.SetMode(ModeStrict)
defer func() {
if r := recover(); r == nil {
t.Error("expected panic for missing key in ModeStrict")
}
}()
svc.T("nonexistent.key.that.does.not.exist")
}

View file

@ -130,3 +130,14 @@ func TestFormatAgo_Good_SingularUnit(t *testing.T) {
got := FormatAgo(1, "fortnight")
assert.Equal(t, "1 fortnight ago", got)
}
// TestTimeAgo_Ugly verifies that a future time (negative duration) does not panic.
//
// TimeAgo(time.Now().Add(10 * time.Minute)) // implementation-defined but no panic
func TestTimeAgo_Ugly(t *testing.T) {
svc, err := New()
require.NoError(t, err)
SetDefault(svc)
got := TimeAgo(time.Now().Add(10 * time.Minute))
_ = got // must not panic
}

View file

@ -124,3 +124,25 @@ func TestToFloat64_Good(t *testing.T) {
})
}
}
// TestGetCount_Bad verifies a map without a Count key returns 0.
//
// getCount(map[string]any{"other": 5}) // 0
func TestGetCount_Bad(t *testing.T) {
data := map[string]any{"other": 5}
assert.Equal(t, 0, getCount(data))
}
// TestToInt_Ugly verifies that nil returns 0 without panic.
//
// toInt(nil) // 0
func TestToInt_Ugly(t *testing.T) {
assert.Equal(t, 0, toInt(nil))
}
// TestToFloat64_Ugly verifies that nil returns 0 without panic.
//
// toFloat64(nil) // 0
func TestToFloat64_Ugly(t *testing.T) {
assert.InDelta(t, 0.0, toFloat64(nil), 0.001)
}

View file

@ -251,3 +251,36 @@ func TestIsRTLLanguage(t *testing.T) {
}
}
}
// TestMessage_Good verifies a simple message returns its text.
//
// Message{Text: "hello"}.ForCategory(PluralOther) // "hello"
func TestMessage_Good(t *testing.T) {
m := Message{Text: "hello"}
if got := m.ForCategory(PluralOther); got != "hello" {
t.Errorf("Message.ForCategory = %q, want %q", got, "hello")
}
}
// TestMessage_Bad verifies an empty message returns empty string.
//
// Message{}.ForCategory(PluralOne) // ""
func TestMessage_Bad(t *testing.T) {
m := Message{}
if got := m.ForCategory(PluralOne); got != "" {
t.Errorf("empty Message.ForCategory = %q, want empty", got)
}
}
// TestMessage_Ugly verifies plural forms are selected by category when both Text and plural forms are set.
//
// Message{Text:"item", One:"item", Other:"items"}.ForCategory(PluralOther) // "items"
func TestMessage_Ugly(t *testing.T) {
m := Message{Text: "item", One: "item", Other: "items"}
if got := m.ForCategory(PluralOne); got != "item" {
t.Errorf("Message.ForCategory(PluralOne) = %q, want %q", got, "item")
}
if got := m.ForCategory(PluralOther); got != "items" {
t.Errorf("Message.ForCategory(PluralOther) = %q, want %q", got, "items")
}
}

View file

@ -60,14 +60,14 @@ func irregularPrompt(verb, tense string) string {
// collectGenerated runs a single-token generation and returns the trimmed, lowercased output.
func collectGenerated(ctx context.Context, m inference.TextModel, prompt string) (string, error) {
sb := core.NewBuilder()
builder := core.NewBuilder()
for tok := range m.Generate(ctx, prompt, inference.WithMaxTokens(1), inference.WithTemperature(0.05)) {
sb.WriteString(tok.Text)
builder.WriteString(tok.Text)
}
if err := m.Err(); err != nil {
return "", err
}
return core.Trim(core.Lower(sb.String())), nil
return core.Trim(core.Lower(builder.String())), nil
}
// ValidateArticle checks whether a given article usage is grammatically correct

View file

@ -336,3 +336,33 @@ func TestIrregularPrompt(t *testing.T) {
t.Errorf("prompt should contain the tense: %q", prompt)
}
}
// TestArticlePrompt_Good verifies the article prompt contains the noun.
//
// articlePrompt("error") // contains "error"
func TestArticlePrompt_Good(t *testing.T) {
prompt := articlePrompt("error")
if !contains(prompt, "error") {
t.Errorf("articlePrompt should contain noun %q, got %q", "error", prompt)
}
}
// TestArticlePrompt_Bad verifies an empty noun still produces a non-empty prompt.
//
// articlePrompt("") // non-empty
func TestArticlePrompt_Bad(t *testing.T) {
prompt := articlePrompt("")
if prompt == "" {
t.Error("articlePrompt(\"\") should not be empty")
}
}
// TestIrregularPrompt_Ugly verifies the prompt is not empty for an unusual tense string.
//
// irregularPrompt("run", "subjunctive") // non-empty
func TestIrregularPrompt_Ugly(t *testing.T) {
prompt := irregularPrompt("run", "subjunctive")
if prompt == "" {
t.Error("irregularPrompt should not be empty for unusual tense")
}
}