1package nuon23import (4 "os/exec"5 "strings"6 "testing"7)89// nu runs a nushell pipeline with input on its standard input, skipping10// the test when nushell isn't installed.11func nu(t *testing.T, script, input string) string {12 t.Helper()13 path, err := exec.LookPath("nu")14 if err != nil {15 t.Skip("nu isn't installed")16 }17 cmd := exec.Command(path, "--no-config-file", "--stdin", "-c", script)18 cmd.Stdin = strings.NewReader(input)19 out, err := cmd.Output()20 if err != nil {21 t.Fatalf("nu -c %q: %v\n%s", script, err, out)22 }23 return strings.TrimSpace(string(out))24}2526// nuTables are pipelines whose output covers nushell's types.27var nuTables = []string{28 `[[name, size, modified]; ["CLAUDE.md", 1646b, 2026-09-27T11:27:31.351890163-06:00], [go.mod, 1.5kb, 2026-01-02]]`,29 `[{a: 1, b: 1.5, c: true, d: null, e: "x y", f: 90sec, g: 0x[DEAD]}, {a: -2, h: [1, 2], i: {j: k}}]`,30 `[[s]; ["true"], ["12"], [""], ["a\"b\nc"], ["-"], [TRUE], ["2026-01-01"], ["1kb"], ["#x"], ["ΓΌ"]]`,31 `{name: 012, when: (1day + 2hr), inf: inf, big: 1e20, small: 1e-7}`,32 `seq 1 5 | each {|i| {i: $i, sq: ($i * $i), half: ($i / 2)}}`,33 `[]`,34}3536// TestRoundTripThroughNu reads what nushell writes and writes it back:37// nushell must read the same values from our text as from its own.38func TestRoundTripThroughNu(t *testing.T) {39 for _, pipeline := range nuTables {40 theirs := nu(t, pipeline+" | to nuon", "")41 v, err := Parse([]byte(theirs))42 if err != nil {43 t.Errorf("%s: parsing nu's %s: %v", pipeline, theirs, err)44 continue45 }46 ours := v.String()47 if back := nu(t, "from nuon | to nuon", ours); back != theirs {48 t.Errorf("%s:\nnu wrote %s\nwe wrote %s\nnu reread %s", pipeline, theirs, ours, back)49 }50 }51}5253// TestReaderReadsNuTables reads nushell's tables row by row and writes54// them with a TableWriter, which nushell reads back as the same table.55func TestReaderReadsNuTables(t *testing.T) {56 for _, pipeline := range nuTables[:3] {57 theirs := nu(t, pipeline+" | to nuon", "")58 rows, _ := rows(t, theirs)59 cols := []string{}60 for _, f := range rows[0] {61 cols = append(cols, f.Key)62 }63 if len(rows[0]) != len(rows[len(rows)-1]) {64 continue // a table whose rows differ is written as records65 }66 var b strings.Builder67 w := NewTableWriter(&b, cols)68 for _, r := range rows {69 vs := make([]Value, len(r))70 for i, f := range r {71 vs[i] = f.Value72 }73 w.Write(vs)74 }75 w.Close()76 if back := nu(t, "from nuon | to nuon", b.String()); back != theirs {77 t.Errorf("%s:\nnu wrote %s\nwe wrote %s\nnu reread %s", pipeline, theirs, b.String(), back)78 }79 }80}8182// TestJSONThroughNu writes JSON that nushell reads as it reads its own.83func TestJSONThroughNu(t *testing.T) {84 theirs := nu(t, nuTables[1]+" | to json -r", "")85 v, err := Parse([]byte(nu(t, nuTables[1]+" | to nuon", "")))86 if err != nil {87 t.Fatal(err)88 }89 if ours := string(AppendJSON(nil, v)); ours != theirs {90 t.Errorf("nu wrote %s\nwe wrote %s", theirs, ours)91 }92}