- убраны комментарии из suite.toml и .conventions.toml: файл, который машина переписывает, комментарий через круг не проносит; объяснения ушли в README рядом, который suite init теперь заводит - удалена текстовая правка манифеста целиком — 520 строк ручного лексера TOML вместе со всем классом ошибок порчи данных - запись идёт из структур энкодером; ключ, которого инструмент не знает, запись останавливает, а не теряется молча - convy sync сверяет манифест и подводит под него раскладку файлов: чего не хватает — собирает, что осиротело — удаляет, копию с локальной частью не трогает никогда
169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
package manifest_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.vakhrushev.me/av/convy/internal/manifest"
|
|
)
|
|
|
|
func write(t *testing.T, dir, name, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(dir, name)
|
|
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// A manifest is data, and a command that changes it rewrites it whole. The
|
|
// second write of the same content has to come out the same, or every command
|
|
// would leave a diff of its own on top of the one it meant.
|
|
func TestSaveIsIdempotent(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, manifest.Name, `[language]
|
|
version = 1
|
|
lang = "ru"
|
|
|
|
[topics.live]
|
|
time = "время"
|
|
|
|
[prefixes.live]
|
|
TIME = "conventions/time.md"
|
|
`)
|
|
|
|
m, err := manifest.Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
once, err := os.ReadFile(m.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
again, err := manifest.Load(dir)
|
|
if err != nil {
|
|
t.Fatalf("the manifest the tool wrote does not load: %v\n%s", err, once)
|
|
}
|
|
if err := again.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
twice, err := os.ReadFile(m.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(once) != string(twice) {
|
|
t.Errorf("the second write differs from the first:\n%s\n---\n%s", once, twice)
|
|
}
|
|
}
|
|
|
|
// A write goes out of the structs, so a key the tool does not know would be
|
|
// dropped. It refuses instead: that neither loses the key nor hides it.
|
|
func TestSaveRefusesWhileAKeyIsUnknown(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, manifest.Name, `[language]
|
|
version = 1
|
|
descriptoin = "LANGUAGE.md"
|
|
`)
|
|
|
|
m, err := manifest.Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before, _ := os.ReadFile(m.Path)
|
|
|
|
err = m.Save()
|
|
if err == nil {
|
|
t.Fatal("the manifest was rewritten over a key the tool does not know")
|
|
}
|
|
if !strings.Contains(err.Error(), "descriptoin") {
|
|
t.Errorf("the refusal does not name the key: %s", err)
|
|
}
|
|
after, _ := os.ReadFile(m.Path)
|
|
if string(before) != string(after) {
|
|
t.Errorf("the file was touched anyway:\n%s", after)
|
|
}
|
|
}
|
|
|
|
// A name never leaves the manifest: it lives on in foreign repositories, and
|
|
// one handed out twice starts pointing at something else there.
|
|
func TestRetireMovesRatherThanDeletes(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, manifest.Name, `[language]
|
|
version = 1
|
|
|
|
[topics.live]
|
|
time = "время"
|
|
logging = "логирование"
|
|
`)
|
|
|
|
m, err := manifest.Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.Topics.Retire("logging", "2026-07-28: свёрнута в errors")
|
|
if err := m.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
back, err := manifest.Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if back.TopicLive("logging") {
|
|
t.Errorf("the topic stayed live")
|
|
}
|
|
if !back.TopicRetired("logging") {
|
|
t.Errorf("the topic is neither live nor retired: the name is loose")
|
|
}
|
|
if !back.TopicLive("time") {
|
|
t.Errorf("the other topic went with it")
|
|
}
|
|
}
|
|
|
|
func TestProjectRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
p := &manifest.Project{
|
|
Source: "../dev-conventions#v2",
|
|
Components: map[string]manifest.Component{
|
|
"backend": {Dir: `docs\conventions`, Lang: []string{"go"}, Topics: []string{"time"}},
|
|
"web": {Dir: "web/docs", Topics: []string{}},
|
|
},
|
|
Path: filepath.Join(dir, manifest.ProjectName),
|
|
Root: dir,
|
|
}
|
|
if err := p.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
back, err := manifest.LoadProject(dir)
|
|
if err != nil {
|
|
t.Fatalf("the manifest the tool wrote does not load: %v", err)
|
|
}
|
|
if back.Source != p.Source {
|
|
t.Errorf("source came back as %q", back.Source)
|
|
}
|
|
// A backslash is the ordinary way a written value fails to read back.
|
|
if got := back.Components["backend"].Dir; got != `docs\conventions` {
|
|
t.Errorf("the directory came back as %q", got)
|
|
}
|
|
if len(back.Components["web"].Lang) != 0 {
|
|
t.Errorf("an empty axis was written and read back as something")
|
|
}
|
|
|
|
back.Subscribe("web", "logging")
|
|
back.Subscribe("web", "errors")
|
|
if got := back.Components["web"].Topics; strings.Join(got, ",") != "errors,logging" {
|
|
t.Errorf("the subscription is not kept in order: %v", got)
|
|
}
|
|
back.Unsubscribe("web", "errors")
|
|
if got := back.Components["web"].Topics; strings.Join(got, ",") != "logging" {
|
|
t.Errorf("unsubscribing left %v", got)
|
|
}
|
|
}
|