1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gzip
6
7import (
8	"bufio"
9	"bytes"
10	"io"
11	"reflect"
12	"testing"
13	"time"
14)
15
16// TestEmpty tests that an empty payload still forms a valid GZIP stream.
17func TestEmpty(t *testing.T) {
18	buf := new(bytes.Buffer)
19
20	if err := NewWriter(buf).Close(); err != nil {
21		t.Fatalf("Writer.Close: %v", err)
22	}
23
24	r, err := NewReader(buf)
25	if err != nil {
26		t.Fatalf("NewReader: %v", err)
27	}
28	if want := (Header{OS: 255}); !reflect.DeepEqual(r.Header, want) {
29		t.Errorf("Header mismatch:\ngot  %#v\nwant %#v", r.Header, want)
30	}
31	b, err := io.ReadAll(r)
32	if err != nil {
33		t.Fatalf("ReadAll: %v", err)
34	}
35	if len(b) != 0 {
36		t.Fatalf("got %d bytes, want 0", len(b))
37	}
38	if err := r.Close(); err != nil {
39		t.Fatalf("Reader.Close: %v", err)
40	}
41}
42
43// TestRoundTrip tests that gzipping and then gunzipping is the identity
44// function.
45func TestRoundTrip(t *testing.T) {
46	buf := new(bytes.Buffer)
47
48	w := NewWriter(buf)
49	w.Comment = "comment"
50	w.Extra = []byte("extra")
51	w.ModTime = time.Unix(1e8, 0)
52	w.Name = "name"
53	if _, err := w.Write([]byte("payload")); err != nil {
54		t.Fatalf("Write: %v", err)
55	}
56	if err := w.Close(); err != nil {
57		t.Fatalf("Writer.Close: %v", err)
58	}
59
60	r, err := NewReader(buf)
61	if err != nil {
62		t.Fatalf("NewReader: %v", err)
63	}
64	b, err := io.ReadAll(r)
65	if err != nil {
66		t.Fatalf("ReadAll: %v", err)
67	}
68	if string(b) != "payload" {
69		t.Fatalf("payload is %q, want %q", string(b), "payload")
70	}
71	if r.Comment != "comment" {
72		t.Fatalf("comment is %q, want %q", r.Comment, "comment")
73	}
74	if string(r.Extra) != "extra" {
75		t.Fatalf("extra is %q, want %q", r.Extra, "extra")
76	}
77	if r.ModTime.Unix() != 1e8 {
78		t.Fatalf("mtime is %d, want %d", r.ModTime.Unix(), uint32(1e8))
79	}
80	if r.Name != "name" {
81		t.Fatalf("name is %q, want %q", r.Name, "name")
82	}
83	if err := r.Close(); err != nil {
84		t.Fatalf("Reader.Close: %v", err)
85	}
86}
87
88// TestLatin1 tests the internal functions for converting to and from Latin-1.
89func TestLatin1(t *testing.T) {
90	latin1 := []byte{0xc4, 'u', 0xdf, 'e', 'r', 'u', 'n', 'g', 0}
91	utf8 := "Äußerung"
92	z := Reader{r: bufio.NewReader(bytes.NewReader(latin1))}
93	s, err := z.readString()
94	if err != nil {
95		t.Fatalf("readString: %v", err)
96	}
97	if s != utf8 {
98		t.Fatalf("read latin-1: got %q, want %q", s, utf8)
99	}
100
101	buf := bytes.NewBuffer(make([]byte, 0, len(latin1)))
102	c := Writer{w: buf}
103	if err = c.writeString(utf8); err != nil {
104		t.Fatalf("writeString: %v", err)
105	}
106	s = buf.String()
107	if s != string(latin1) {
108		t.Fatalf("write utf-8: got %q, want %q", s, string(latin1))
109	}
110}
111
112// TestLatin1RoundTrip tests that metadata that is representable in Latin-1
113// survives a round trip.
114func TestLatin1RoundTrip(t *testing.T) {
115	testCases := []struct {
116		name string
117		ok   bool
118	}{
119		{"", true},
120		{"ASCII is OK", true},
121		{"unless it contains a NUL\x00", false},
122		{"no matter where \x00 occurs", false},
123		{"\x00\x00\x00", false},
124		{"Látin-1 also passes (U+00E1)", true},
125		{"but LĀtin Extended-A (U+0100) does not", false},
126		{"neither does 日本語", false},
127		{"invalid UTF-8 also \xffails", false},
128		{"\x00 as does Látin-1 with NUL", false},
129	}
130	for _, tc := range testCases {
131		buf := new(bytes.Buffer)
132
133		w := NewWriter(buf)
134		w.Name = tc.name
135		err := w.Close()
136		if (err == nil) != tc.ok {
137			t.Errorf("Writer.Close: name = %q, err = %v", tc.name, err)
138			continue
139		}
140		if !tc.ok {
141			continue
142		}
143
144		r, err := NewReader(buf)
145		if err != nil {
146			t.Errorf("NewReader: %v", err)
147			continue
148		}
149		_, err = io.ReadAll(r)
150		if err != nil {
151			t.Errorf("ReadAll: %v", err)
152			continue
153		}
154		if r.Name != tc.name {
155			t.Errorf("name is %q, want %q", r.Name, tc.name)
156			continue
157		}
158		if err := r.Close(); err != nil {
159			t.Errorf("Reader.Close: %v", err)
160			continue
161		}
162	}
163}
164
165func TestWriterFlush(t *testing.T) {
166	buf := new(bytes.Buffer)
167
168	w := NewWriter(buf)
169	w.Comment = "comment"
170	w.Extra = []byte("extra")
171	w.ModTime = time.Unix(1e8, 0)
172	w.Name = "name"
173
174	n0 := buf.Len()
175	if n0 != 0 {
176		t.Fatalf("buffer size = %d before writes; want 0", n0)
177	}
178
179	if err := w.Flush(); err != nil {
180		t.Fatal(err)
181	}
182
183	n1 := buf.Len()
184	if n1 == 0 {
185		t.Fatal("no data after first flush")
186	}
187
188	w.Write([]byte("x"))
189
190	n2 := buf.Len()
191	if n1 != n2 {
192		t.Fatalf("after writing a single byte, size changed from %d to %d; want no change", n1, n2)
193	}
194
195	if err := w.Flush(); err != nil {
196		t.Fatal(err)
197	}
198
199	n3 := buf.Len()
200	if n2 == n3 {
201		t.Fatal("Flush didn't flush any data")
202	}
203
204	if err := w.Close(); err != nil {
205		t.Fatal(err)
206	}
207
208}
209
210// Multiple gzip files concatenated form a valid gzip file.
211func TestConcat(t *testing.T) {
212	var buf bytes.Buffer
213	w := NewWriter(&buf)
214	w.Write([]byte("hello "))
215	w.Close()
216	w = NewWriter(&buf)
217	w.Write([]byte("world\n"))
218	w.Close()
219
220	r, err := NewReader(&buf)
221	if err != nil {
222		t.Fatal(err)
223	}
224	data, err := io.ReadAll(r)
225	if string(data) != "hello world\n" || err != nil {
226		t.Fatalf("ReadAll = %q, %v, want %q, nil", data, err, "hello world")
227	}
228}
229
230func TestWriterReset(t *testing.T) {
231	buf := new(bytes.Buffer)
232	buf2 := new(bytes.Buffer)
233	z := NewWriter(buf)
234	msg := []byte("hello world")
235	z.Write(msg)
236	z.Close()
237	z.Reset(buf2)
238	z.Write(msg)
239	z.Close()
240	if buf.String() != buf2.String() {
241		t.Errorf("buf2 %q != original buf of %q", buf2.String(), buf.String())
242	}
243}
244
245type limitedWriter struct {
246	N int
247}
248
249func (l *limitedWriter) Write(p []byte) (n int, err error) {
250	if n := l.N; n < len(p) {
251		l.N = 0
252		return n, io.ErrShortWrite
253	}
254	l.N -= len(p)
255	return len(p), nil
256}
257
258// Write should never return more bytes than the input slice.
259func TestLimitedWrite(t *testing.T) {
260	msg := []byte("a")
261
262	for lim := 2; lim < 20; lim++ {
263		z := NewWriter(&limitedWriter{lim})
264		if n, _ := z.Write(msg); n > len(msg) {
265			t.Errorf("Write() = %d, want %d or less", n, len(msg))
266		}
267
268		z.Reset(&limitedWriter{lim})
269		z.Header = Header{
270			Comment: "comment",
271			Extra:   []byte("extra"),
272			ModTime: time.Now(),
273			Name:    "name",
274			OS:      1,
275		}
276		if n, _ := z.Write(msg); n > len(msg) {
277			t.Errorf("Write() = %d, want %d or less", n, len(msg))
278		}
279	}
280}
281