mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 22:12:32 +08:00
docs-i18n: add behavior baseline fixtures
This commit is contained in:
206
scripts/docs-i18n/behavior_baseline_test.go
Normal file
206
scripts/docs-i18n/behavior_baseline_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type behaviorReplacePair struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
type behaviorRule struct {
|
||||
Method string `json:"method"`
|
||||
MatchAll []string `json:"match_all"`
|
||||
ResponseFile string `json:"response_file,omitempty"`
|
||||
ReplacePairs []behaviorReplacePair `json:"replace_pairs,omitempty"`
|
||||
}
|
||||
|
||||
type behaviorFixture struct {
|
||||
Name string `json:"name"`
|
||||
Mode string `json:"mode"`
|
||||
RelPath string `json:"rel_path"`
|
||||
SourceFile string `json:"source_file"`
|
||||
ExpectedFile string `json:"expected_file,omitempty"`
|
||||
ExpectedErrorContains string `json:"expected_error_contains,omitempty"`
|
||||
ExpectedOutputContains []string `json:"expected_output_contains,omitempty"`
|
||||
ExpectedOutputNotContains []string `json:"expected_output_not_contains,omitempty"`
|
||||
Rules []behaviorRule `json:"rules"`
|
||||
}
|
||||
|
||||
type behaviorFixtureTranslator struct {
|
||||
t *testing.T
|
||||
dir string
|
||||
rules []behaviorRule
|
||||
}
|
||||
|
||||
func (t *behaviorFixtureTranslator) Translate(_ context.Context, text, _, _ string) (string, error) {
|
||||
return t.run("masked", text), nil
|
||||
}
|
||||
|
||||
func (t *behaviorFixtureTranslator) TranslateRaw(_ context.Context, text, _, _ string) (string, error) {
|
||||
return t.run("raw", text), nil
|
||||
}
|
||||
|
||||
func (t *behaviorFixtureTranslator) Close() {}
|
||||
|
||||
func (t *behaviorFixtureTranslator) run(method, text string) string {
|
||||
t.t.Helper()
|
||||
for _, rule := range t.rules {
|
||||
if rule.Method != method {
|
||||
continue
|
||||
}
|
||||
if !matchesAll(text, rule.MatchAll) {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case rule.ResponseFile != "":
|
||||
return readFixtureText(t.t, filepath.Join(t.dir, rule.ResponseFile))
|
||||
case len(rule.ReplacePairs) > 0:
|
||||
out := text
|
||||
for _, pair := range rule.ReplacePairs {
|
||||
out = strings.ReplaceAll(out, pair.From, pair.To)
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return text
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func matchesAll(text string, fragments []string) bool {
|
||||
for _, fragment := range fragments {
|
||||
if !strings.Contains(text, fragment) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestDocsI18nBehaviorBaselines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := filepath.Join("testdata", "behavior")
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadDir(%q): %v", root, err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
dir := filepath.Join(root, entry.Name())
|
||||
fixture := loadBehaviorFixture(t, dir)
|
||||
name := fixture.Name
|
||||
if name == "" {
|
||||
name = entry.Name()
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runBehaviorFixture(t, dir, fixture)
|
||||
})
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("no behavior fixtures found under %s", root)
|
||||
}
|
||||
}
|
||||
|
||||
func loadBehaviorFixture(t *testing.T, dir string) behaviorFixture {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(filepath.Join(dir, "case.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile(case.json): %v", err)
|
||||
}
|
||||
var fixture behaviorFixture
|
||||
if err := json.Unmarshal(data, &fixture); err != nil {
|
||||
t.Fatalf("Unmarshal(case.json): %v", err)
|
||||
}
|
||||
return fixture
|
||||
}
|
||||
|
||||
func runBehaviorFixture(t *testing.T, dir string, fixture behaviorFixture) {
|
||||
t.Helper()
|
||||
|
||||
source := readFixtureText(t, filepath.Join(dir, fixture.SourceFile))
|
||||
translator := &behaviorFixtureTranslator{
|
||||
t: t,
|
||||
dir: dir,
|
||||
rules: fixture.Rules,
|
||||
}
|
||||
|
||||
var (
|
||||
got string
|
||||
err error
|
||||
)
|
||||
|
||||
switch fixture.Mode {
|
||||
case "doc_body_chunked":
|
||||
got, err = translateDocBodyChunked(context.Background(), translator, fixture.RelPath, source, "en", "zh-CN")
|
||||
case "frontmatter_scalar":
|
||||
got, err = translateSnippet(
|
||||
context.Background(),
|
||||
translator,
|
||||
&TranslationMemory{entries: map[string]TMEntry{}},
|
||||
fixture.RelPath+":frontmatter:title",
|
||||
source,
|
||||
"en",
|
||||
"zh-CN",
|
||||
)
|
||||
default:
|
||||
t.Fatalf("unsupported fixture mode %q", fixture.Mode)
|
||||
}
|
||||
|
||||
if fixture.ExpectedErrorContains != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error containing %q, got nil", fixture.ExpectedErrorContains)
|
||||
}
|
||||
if !strings.Contains(err.Error(), fixture.ExpectedErrorContains) {
|
||||
t.Fatalf("expected error containing %q, got %v", fixture.ExpectedErrorContains, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if fixture.ExpectedFile != "" {
|
||||
want := readFixtureText(t, filepath.Join(dir, fixture.ExpectedFile))
|
||||
if normalizeBehaviorText(got) != normalizeBehaviorText(want) {
|
||||
t.Fatalf("unexpected output\nwant:\n%s\n\ngot:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fragment := range fixture.ExpectedOutputContains {
|
||||
if !strings.Contains(got, fragment) {
|
||||
t.Fatalf("expected output to contain %q\noutput:\n%s", fragment, got)
|
||||
}
|
||||
}
|
||||
for _, fragment := range fixture.ExpectedOutputNotContains {
|
||||
if strings.Contains(got, fragment) {
|
||||
t.Fatalf("expected output to exclude %q\noutput:\n%s", fragment, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readFixtureText(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile(%q): %v", path, err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func normalizeBehaviorText(value string) string {
|
||||
return strings.TrimSpace(strings.ReplaceAll(value, "\r\n", "\n"))
|
||||
}
|
||||
24
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/case.json
vendored
Normal file
24
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/case.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "fenced singleton retries after malformed raw output",
|
||||
"mode": "doc_body_chunked",
|
||||
"rel_path": "gateway/configuration-reference.md",
|
||||
"source_file": "source.txt",
|
||||
"expected_output_contains": [
|
||||
"Translated line 01",
|
||||
"Translated line 02",
|
||||
"Translated line 03",
|
||||
"Translated line 04"
|
||||
],
|
||||
"expected_output_not_contains": ["Line 01", "Line 02", "Line 03", "Line 04"],
|
||||
"rules": [
|
||||
{
|
||||
"method": "raw",
|
||||
"match_all": ["Line 01", "Line 04"],
|
||||
"response_file": "raw-malformed.txt"
|
||||
},
|
||||
{
|
||||
"method": "raw",
|
||||
"replace_pairs": [{ "from": "Line ", "to": "Translated line " }]
|
||||
}
|
||||
]
|
||||
}
|
||||
7
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/expected.txt
vendored
Normal file
7
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/expected.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
```md
|
||||
Translated line 01
|
||||
Translated line 02
|
||||
Translated line 03
|
||||
Translated line 04
|
||||
```
|
||||
|
||||
6
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/raw-malformed.txt
vendored
Normal file
6
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/raw-malformed.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
```md
|
||||
Line 01
|
||||
Line 02
|
||||
Line 03
|
||||
Line 04
|
||||
|
||||
7
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/source.txt
vendored
Normal file
7
scripts/docs-i18n/testdata/behavior/fenced-singleton-retry/source.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
```md
|
||||
Line 01
|
||||
Line 02
|
||||
Line 03
|
||||
Line 04
|
||||
```
|
||||
|
||||
14
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/case.json
vendored
Normal file
14
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/case.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "frontmatter scalar falls back instead of keeping tagged wrapper",
|
||||
"mode": "frontmatter_scalar",
|
||||
"rel_path": "install/fly.md",
|
||||
"source_file": "source.txt",
|
||||
"expected_file": "expected.txt",
|
||||
"rules": [
|
||||
{
|
||||
"method": "masked",
|
||||
"match_all": ["Deploying OpenClaw on Fly.io"],
|
||||
"response_file": "masked-tagged-wrapper.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/expected.txt
vendored
Normal file
1
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/expected.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Deploying OpenClaw on Fly.io
|
||||
7
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/masked-tagged-wrapper.txt
vendored
Normal file
7
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/masked-tagged-wrapper.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<frontmatter>
|
||||
title: Fly.io
|
||||
</frontmatter>
|
||||
|
||||
<body>
|
||||
# Fly.io 部署
|
||||
</body>
|
||||
1
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/source.txt
vendored
Normal file
1
scripts/docs-i18n/testdata/behavior/frontmatter-fallback/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Deploying OpenClaw on Fly.io
|
||||
25
scripts/docs-i18n/testdata/behavior/protocol-leak-split/case.json
vendored
Normal file
25
scripts/docs-i18n/testdata/behavior/protocol-leak-split/case.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "protocol leak retries on smaller chunks",
|
||||
"mode": "doc_body_chunked",
|
||||
"rel_path": "gateway/configuration-reference.md",
|
||||
"source_file": "source.txt",
|
||||
"expected_file": "expected.txt",
|
||||
"expected_output_not_contains": ["<frontmatter>", "<body>", "[[[FM_"],
|
||||
"rules": [
|
||||
{
|
||||
"method": "raw",
|
||||
"match_all": ["First chunk", "Second chunk"],
|
||||
"response_file": "raw-leaked.txt"
|
||||
},
|
||||
{
|
||||
"method": "raw",
|
||||
"match_all": ["First chunk"],
|
||||
"response_file": "raw-first.txt"
|
||||
},
|
||||
{
|
||||
"method": "raw",
|
||||
"match_all": ["Second chunk"],
|
||||
"response_file": "raw-second.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
scripts/docs-i18n/testdata/behavior/protocol-leak-split/expected.txt
vendored
Normal file
4
scripts/docs-i18n/testdata/behavior/protocol-leak-split/expected.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
First translated
|
||||
|
||||
Second translated
|
||||
|
||||
2
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-first.txt
vendored
Normal file
2
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-first.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
First translated
|
||||
|
||||
9
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-leaked.txt
vendored
Normal file
9
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-leaked.txt
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<frontmatter>
|
||||
title: leaked
|
||||
</frontmatter>
|
||||
|
||||
<body>
|
||||
First translated
|
||||
|
||||
Second translated
|
||||
</body>
|
||||
2
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-second.txt
vendored
Normal file
2
scripts/docs-i18n/testdata/behavior/protocol-leak-split/raw-second.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Second translated
|
||||
|
||||
4
scripts/docs-i18n/testdata/behavior/protocol-leak-split/source.txt
vendored
Normal file
4
scripts/docs-i18n/testdata/behavior/protocol-leak-split/source.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
First chunk
|
||||
|
||||
Second chunk
|
||||
|
||||
14
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/case.json
vendored
Normal file
14
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/case.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "uppercase body wrapper is stripped",
|
||||
"mode": "doc_body_chunked",
|
||||
"rel_path": "help/testing.md",
|
||||
"source_file": "source.txt",
|
||||
"expected_file": "expected.txt",
|
||||
"rules": [
|
||||
{
|
||||
"method": "raw",
|
||||
"match_all": ["Regular paragraph."],
|
||||
"response_file": "raw-uppercase-wrapper.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/expected.txt
vendored
Normal file
1
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/expected.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Translated paragraph.
|
||||
3
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/raw-uppercase-wrapper.txt
vendored
Normal file
3
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/raw-uppercase-wrapper.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
Translated paragraph.
|
||||
</BODY>
|
||||
1
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/source.txt
vendored
Normal file
1
scripts/docs-i18n/testdata/behavior/uppercase-wrapper-strip/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Regular paragraph.
|
||||
Reference in New Issue
Block a user