hello world`,
		parsed: &Archive{
			Comment: []byte("comment1\ncomment2\n"),
			Files: []File{
				{"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},
				{"file 2", []byte("File 2 text.\n")},
				{"empty", []byte{}},
				{"noNL", []byte("hello world\n")},
			},
		},
	},
	// Test CRLF input
	{
		name: "basic",
		text: "blah\r\n-- hello --\r\nhello\r\n",
		parsed: &Archive{
			Comment: []byte("blah\r\n"),
			Files: []File{
				{"hello", []byte("hello\r\n")},
			},
		},
	},
}

func Test(t *testing.T) {
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			a := Parse([]byte(tt.text))
			if !reflect.DeepEqual(a, tt.parsed) {
				t.Fatalf("Parse: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
			}
			text := Format(a)
			a = Parse(text)
			if !reflect.DeepEqual(a, tt.parsed) {
				t.Fatalf("Parse after Format: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
			}
		})
	}
}

func shortArchive(a *Archive) string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "comment: %q\n", a.Comment)
	for _, f := range a.Files {
		fmt.Fprintf(&buf, "file %q: %q\n", f.Name, f.Data)
	}
	return buf.String()
}

func TestWrite(t *testing.T) {
	td, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(td)

	good := &Archive{Files: []File{File{Name: "good.txt"}}}
	if err := Write(good, td); err != nil {
		t.Fatalf("expected no error; got %v", err)
	}

	badRel := &Archive{Files: []File{File{Name: "../bad.txt"}}}
	want := `"../bad.txt": outside parent directory`
	if err := Write(badRel, td); err == nil || err.Error() != want {
		t.Fatalf("expected %v; got %v", want, err)
	}

	badAbs := &Archive{Files: []File{File{Name: "/bad.txt"}}}
	want = `"/bad.txt": outside parent directory`
	if err := Write(badAbs, td); err == nil || err.Error() != want {
		t.Fatalf("expected %v; got %v", want, err)
	}
}

var unquoteErrorTests = []struct {
	testName    string
	data        string
	expectError string
}{{
	testName:    "no final newline",
	data:        ">hello",
	expectError: `data does not appear to be quoted`,
}, {
	testName:    "no initial >",
	data:        "hello\n",
	expectError: `data does not appear to be quoted`,
}}

func TestUnquote(t *testing.T) {
	for _, test := range unquoteErrorTests {
		t.Run(test.testName, func(t *testing.T) {
			_, err := Unquote([]byte(test.data))
			if err == nil {
				t.Fatalf("unexpected success")
			}
			if err.Error() != test.expectError {
				t.Fatalf("unexpected error; got %q want %q", err, test.expectError)
			}
		})
	}
}

var quoteTests = []struct {
	testName    string
	data        string
	expect      string
	expectError string
}{{
	testName: "empty",
	data:     "",
	expect:   "",
}, {
	testName: "one line",
	data:     "foo\n",
	expect:   ">foo\n",
}, {
	testName: "several lines",
	data:     "foo\nbar\n-- baz --\n",
	expect:   ">foo\n>bar\n>-- baz --\n",
}, {
	testName:    "bad data",
	data:        "foo\xff\n",
	expectError: `data contains non-UTF-8 characters`,
}, {
	testName:    "no final newline",
	data:        "foo",
	expectError: `data has no final newline`,
}}

func TestQuote(t *testing.T) {
	for _, test := range quoteTests {
		t.Run(test.testName, func(t *testing.T) {
			got, err := Quote([]byte(test.data))
			if test.expectError != "" {
				if err == nil {
					t.Fatalf("unexpected success")
				}
				if err.Error() != test.expectError {
					t.Fatalf("unexpected error; got %q want %q", err, test.expectError)
				}
				return
			}
			if err != nil {
				t.Fatalf("quote error: %v", err)
			}
			if string(got) != test.expect {
				t.Fatalf("unexpected result; got %q want %q", got, test.expect)
			}
			orig, err := Unquote(got)
			if err != nil {
				t.Fatal(err)
			}
			if string(orig) != test.data {
				t.Fatalf("round trip failed; got %q want %q", orig, test.data)
			}
		})
	}
}
