исправлены находки ревью проектной стороны
- манифест читается так, как записан: решётка внутри строки не открывает комментарий, скобка внутри комментария не закрывает массив, имя внутри комментария не становится подпиской; новый ключ встаёт после массива, а не внутрь него - всё записываемое проходит через manifest.Quote — обратный слэш в пути делал файл, который инструмент сам не читает - маркер локальной части переехал в doc и пропускает огороженные блоки: процитированный в примере маркер больше не считается границей, а копия без маркера не перезаписывается молча - лишний позиционный аргумент отсекается: flag прекращал разбор и прятал флаги после себя, из-за чего pull, list и check игнорировали --for - заведены тесты проверок копий, включая молчание на исправной копии
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.vakhrushev.me/av/convy/internal/check"
|
||||
"git.vakhrushev.me/av/convy/internal/cli"
|
||||
"git.vakhrushev.me/av/convy/internal/doc"
|
||||
)
|
||||
|
||||
// readingGuide stands in for what a suite puts next to its copies: the short
|
||||
@@ -343,6 +347,11 @@ func TestTheLanguageMayLiveApartFromTheSuite(t *testing.T) {
|
||||
}
|
||||
checkClean(t, suiteRoot)
|
||||
|
||||
// A check that did not run says so: silence would read as a check passed.
|
||||
if _, out := run(t, suiteRoot, "", false, "suite", "check"); !strings.Contains(out, "went unchecked") {
|
||||
t.Errorf("the check did not say it left the language alone:\n%s", out)
|
||||
}
|
||||
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
if code, out := run(t, root, "", false, "add", "time"); code != cli.OK {
|
||||
t.Fatalf("convy add returned %d: %s", code, out)
|
||||
@@ -385,3 +394,134 @@ func TestCheckCatchesALocalRuleAboveTheMarker(t *testing.T) {
|
||||
t.Errorf("the finding does not say what is at stake:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// copyClean is the project half of the invariant worth the most: what the tool
|
||||
// writes, the tool accepts — and accepts in silence, warnings included.
|
||||
func copyClean(t *testing.T, root, dir string) {
|
||||
t.Helper()
|
||||
var docs []*doc.Document
|
||||
entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(dir)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, e := range entries {
|
||||
if filepath.Ext(e.Name()) != ".md" {
|
||||
continue
|
||||
}
|
||||
rel := path.Join(dir, e.Name())
|
||||
d, err := doc.Load(rel, filepath.Join(root, filepath.FromSlash(rel)))
|
||||
if err != nil {
|
||||
t.Fatalf("loading %s: %v", rel, err)
|
||||
}
|
||||
if d.Front.Origin != "" {
|
||||
docs = append(docs, d)
|
||||
}
|
||||
}
|
||||
if len(docs) == 0 {
|
||||
t.Fatalf("no copy was assembled in %s", dir)
|
||||
}
|
||||
rep := check.Copies(docs)
|
||||
if len(rep.Findings()) == 0 {
|
||||
return
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, f := range rep.Findings() {
|
||||
fmt.Fprintf(&b, " %s: %s\n", f.Path, f.Msg)
|
||||
}
|
||||
t.Fatalf("the copies the tool assembled do not check clean:\n%s", b.String())
|
||||
}
|
||||
|
||||
func TestWhatTheToolAssemblesTheToolAccepts(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
run(t, root, "", false, "add", "time")
|
||||
run(t, root, "", false, "add", "logging")
|
||||
copyClean(t, root, "docs/conventions")
|
||||
}
|
||||
|
||||
// The flag package stops at the first argument that is not a flag, so a stray
|
||||
// one hides every flag written after it and the command quietly does something
|
||||
// else than what was asked.
|
||||
func TestProjectCommandsTurnDownAStrayArgument(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
|
||||
for _, args := range [][]string{
|
||||
{"pull", "backend"},
|
||||
{"list", "backend"},
|
||||
{"check", "backend"},
|
||||
{"init", "somewhere"},
|
||||
} {
|
||||
t.Run(args[0], func(t *testing.T) {
|
||||
code, out := run(t, root, "", false, args...)
|
||||
if code == cli.OK {
|
||||
t.Fatalf("the stray argument went through:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "takes no argument") {
|
||||
t.Errorf("the refusal does not say what is wrong:\n%s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The marker is always left by the assembler, so a copy without one was edited
|
||||
// by hand — and everything in it would count as suite text to be replaced.
|
||||
func TestPullRefusesACopyWhoseMarkerIsGone(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
run(t, root, "", false, "add", "time")
|
||||
|
||||
name := filepath.Join(root, "docs", "conventions", "time.md")
|
||||
body := read(t, root, "docs/conventions/time.md")
|
||||
body = strings.Replace(body, "<!-- conv:local -->", "Заметка, написанная руками.", 1)
|
||||
if err := os.WriteFile(name, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code, out := run(t, root, "", false, "pull")
|
||||
if code == cli.OK {
|
||||
t.Fatalf("the pull went ahead over a hand-edited copy:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "carries no <!-- conv:local --> marker") {
|
||||
t.Errorf("the refusal does not say why:\n%s", out)
|
||||
}
|
||||
if got := read(t, root, "docs/conventions/time.md"); !strings.Contains(got, "Заметка, написанная руками") {
|
||||
t.Errorf("the hand-written text was destroyed anyway:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever the tool writes into the manifest it has to read back, and a
|
||||
// backslash is the ordinary way that fails.
|
||||
func TestInitWritesAManifestItCanRead(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", `docs\conventions`)
|
||||
|
||||
code, out := run(t, root, "", false, "list")
|
||||
if code != cli.OK {
|
||||
t.Fatalf("the manifest the tool wrote does not parse: %d\n%s", code, out)
|
||||
}
|
||||
if !strings.Contains(out, `docs\conventions`) {
|
||||
t.Errorf("the directory came back changed:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// A typo in a key costs a whole component, and the parser knows about it — so
|
||||
// every command that reads the manifest has to pass that on.
|
||||
func TestCheckSaysWhatItDidNotUnderstand(t *testing.T) {
|
||||
suiteRoot := subscribable(t)
|
||||
root := wired(t, suiteRoot, "--component", "backend", "--dir", "docs/conventions", "--lang", "go")
|
||||
|
||||
body := read(t, root, ".conventions.toml")
|
||||
body = strings.Replace(body, `dir = "docs/conventions"`, `dyr = "docs/conventions"`, 1)
|
||||
if err := os.WriteFile(filepath.Join(root, ".conventions.toml"), []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code, out := run(t, root, "", false, "check")
|
||||
if !strings.Contains(out, "dyr") {
|
||||
t.Errorf("the typo in the manifest went unmentioned:\n%s", out)
|
||||
}
|
||||
if code == cli.OK {
|
||||
t.Errorf("a component with no dir was checked anyway:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user