src/archive/zip/writer_test.go GO 674 lines View on github.com → Search inside
1// Copyright 2011 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package zip67import (8	"bytes"9	"compress/flate"10	"encoding/binary"11	"fmt"12	"hash/crc32"13	"io"14	"io/fs"15	"math/rand"16	"os"17	"strings"18	"testing"19	"testing/fstest"20	"time"21)2223// TODO(adg): a more sophisticated test suite2425type WriteTest struct {26	Name   string27	Data   []byte28	Method uint1629	Mode   fs.FileMode30}3132var writeTests = []WriteTest{33	{34		Name:   "foo",35		Data:   []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),36		Method: Store,37		Mode:   0666,38	},39	{40		Name:   "bar",41		Data:   nil, // large data set in the test42		Method: Deflate,43		Mode:   0644,44	},45	{46		Name:   "setuid",47		Data:   []byte("setuid file"),48		Method: Deflate,49		Mode:   0755 | fs.ModeSetuid,50	},51	{52		Name:   "setgid",53		Data:   []byte("setgid file"),54		Method: Deflate,55		Mode:   0755 | fs.ModeSetgid,56	},57	{58		Name:   "symlink",59		Data:   []byte("../link/target"),60		Method: Deflate,61		Mode:   0755 | fs.ModeSymlink,62	},63	{64		Name:   "device",65		Data:   []byte("device file"),66		Method: Deflate,67		Mode:   0755 | fs.ModeDevice,68	},69	{70		Name:   "chardevice",71		Data:   []byte("char device file"),72		Method: Deflate,73		Mode:   0755 | fs.ModeDevice | fs.ModeCharDevice,74	},75}7677func TestWriter(t *testing.T) {78	largeData := make([]byte, 1<<17)79	if _, err := rand.Read(largeData); err != nil {80		t.Fatal("rand.Read failed:", err)81	}82	writeTests[1].Data = largeData83	defer func() {84		writeTests[1].Data = nil85	}()8687	// write a zip file88	buf := new(bytes.Buffer)89	w := NewWriter(buf)9091	for _, wt := range writeTests {92		testCreate(t, w, &wt)93	}9495	if err := w.Close(); err != nil {96		t.Fatal(err)97	}9899	// read it back100	r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))101	if err != nil {102		t.Fatal(err)103	}104	for i, wt := range writeTests {105		testReadFile(t, r.File[i], &wt)106	}107}108109// TestWriterComment is test for EOCD comment read/write.110func TestWriterComment(t *testing.T) {111	tests := []struct {112		comment string113		ok      bool114	}{115		{"hi, hello", true},116		{"hi, こんにちわ", true},117		{strings.Repeat("a", uint16max), true},118		{strings.Repeat("a", uint16max+1), false},119	}120121	for _, test := range tests {122		// write a zip file123		buf := new(bytes.Buffer)124		w := NewWriter(buf)125		if err := w.SetComment(test.comment); err != nil {126			if test.ok {127				t.Fatalf("SetComment: unexpected error %v", err)128			}129			continue130		} else {131			if !test.ok {132				t.Fatalf("SetComment: unexpected success, want error")133			}134		}135136		if err := w.Close(); test.ok == (err != nil) {137			t.Fatal(err)138		}139140		if w.closed != test.ok {141			t.Fatalf("Writer.closed: got %v, want %v", w.closed, test.ok)142		}143144		// skip read test in failure cases145		if !test.ok {146			continue147		}148149		// read it back150		r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))151		if err != nil {152			t.Fatal(err)153		}154		if r.Comment != test.comment {155			t.Fatalf("Reader.Comment: got %v, want %v", r.Comment, test.comment)156		}157	}158}159160func TestWriterUTF8(t *testing.T) {161	utf8Tests := []struct {162		name    string163		comment string164		nonUTF8 bool165		flags   uint16166	}{167		{168			name:    "hi, hello",169			comment: "in the world",170			flags:   0x8,171		},172		{173			name:    "hi, こんにちわ",174			comment: "in the world",175			flags:   0x808,176		},177		{178			name:    "hi, こんにちわ",179			comment: "in the world",180			nonUTF8: true,181			flags:   0x8,182		},183		{184			name:    "hi, hello",185			comment: "in the 世界",186			flags:   0x808,187		},188		{189			name:    "hi, こんにちわ",190			comment: "in the 世界",191			flags:   0x808,192		},193		{194			name:    "the replacement rune is �",195			comment: "the replacement rune is �",196			flags:   0x808,197		},198		{199			// Name is Japanese encoded in Shift JIS.200			name:    "\x93\xfa\x96{\x8c\xea.txt",201			comment: "in the 世界",202			flags:   0x008, // UTF-8 must not be set203		},204	}205206	// write a zip file207	buf := new(bytes.Buffer)208	w := NewWriter(buf)209210	for _, test := range utf8Tests {211		h := &FileHeader{212			Name:    test.name,213			Comment: test.comment,214			NonUTF8: test.nonUTF8,215			Method:  Deflate,216		}217		w, err := w.CreateHeader(h)218		if err != nil {219			t.Fatal(err)220		}221		w.Write([]byte{})222	}223224	if err := w.Close(); err != nil {225		t.Fatal(err)226	}227228	// read it back229	r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))230	if err != nil {231		t.Fatal(err)232	}233	for i, test := range utf8Tests {234		flags := r.File[i].Flags235		if flags != test.flags {236			t.Errorf("CreateHeader(name=%q comment=%q nonUTF8=%v): flags=%#x, want %#x", test.name, test.comment, test.nonUTF8, flags, test.flags)237		}238	}239}240241func TestWriterTime(t *testing.T) {242	var buf bytes.Buffer243	h := &FileHeader{244		Name:     "test.txt",245		Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)),246	}247	w := NewWriter(&buf)248	if _, err := w.CreateHeader(h); err != nil {249		t.Fatalf("unexpected CreateHeader error: %v", err)250	}251	if err := w.Close(); err != nil {252		t.Fatalf("unexpected Close error: %v", err)253	}254255	want, err := os.ReadFile("testdata/time-go.zip")256	if err != nil {257		t.Fatalf("unexpected ReadFile error: %v", err)258	}259	if got := buf.Bytes(); !bytes.Equal(got, want) {260		fmt.Printf("%x\n%x\n", got, want)261		t.Error("contents of time-go.zip differ")262	}263}264265func TestWriterOffset(t *testing.T) {266	largeData := make([]byte, 1<<17)267	if _, err := rand.Read(largeData); err != nil {268		t.Fatal("rand.Read failed:", err)269	}270	writeTests[1].Data = largeData271	defer func() {272		writeTests[1].Data = nil273	}()274275	// write a zip file276	buf := new(bytes.Buffer)277	existingData := []byte{1, 2, 3, 1, 2, 3, 1, 2, 3}278	n, _ := buf.Write(existingData)279	w := NewWriter(buf)280	w.SetOffset(int64(n))281282	for _, wt := range writeTests {283		testCreate(t, w, &wt)284	}285286	if err := w.Close(); err != nil {287		t.Fatal(err)288	}289290	// read it back291	r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))292	if err != nil {293		t.Fatal(err)294	}295	for i, wt := range writeTests {296		testReadFile(t, r.File[i], &wt)297	}298}299300func TestWriterFlush(t *testing.T) {301	var buf bytes.Buffer302	w := NewWriter(struct{ io.Writer }{&buf})303	_, err := w.Create("foo")304	if err != nil {305		t.Fatal(err)306	}307	if buf.Len() > 0 {308		t.Fatalf("Unexpected %d bytes already in buffer", buf.Len())309	}310	if err := w.Flush(); err != nil {311		t.Fatal(err)312	}313	if buf.Len() == 0 {314		t.Fatal("No bytes written after Flush")315	}316}317318func TestWriterDir(t *testing.T) {319	w := NewWriter(io.Discard)320	dw, err := w.Create("dir/")321	if err != nil {322		t.Fatal(err)323	}324	if _, err := dw.Write(nil); err != nil {325		t.Errorf("Write(nil) to directory: got %v, want nil", err)326	}327	if _, err := dw.Write([]byte("hello")); err == nil {328		t.Error(`Write("hello") to directory: got nil error, want non-nil`)329	}330}331332func TestWriterDirAttributes(t *testing.T) {333	var buf bytes.Buffer334	w := NewWriter(&buf)335	if _, err := w.CreateHeader(&FileHeader{336		Name:               "dir/",337		Method:             Deflate,338		CompressedSize64:   1234,339		UncompressedSize64: 5678,340	}); err != nil {341		t.Fatal(err)342	}343	if err := w.Close(); err != nil {344		t.Fatal(err)345	}346	b := buf.Bytes()347348	var sig [4]byte349	binary.LittleEndian.PutUint32(sig[:], uint32(fileHeaderSignature))350351	idx := bytes.Index(b, sig[:])352	if idx == -1 {353		t.Fatal("file header not found")354	}355	b = b[idx:]356357	if !bytes.Equal(b[6:10], []byte{0, 0, 0, 0}) { // FileHeader.Flags: 0, FileHeader.Method: 0358		t.Errorf("unexpected method and flags: %v", b[6:10])359	}360361	if !bytes.Equal(b[14:26], make([]byte, 12)) { // FileHeader.{CRC32,CompressSize,UncompressedSize} all zero.362		t.Errorf("unexpected crc, compress and uncompressed size to be 0 was: %v", b[14:26])363	}364365	binary.LittleEndian.PutUint32(sig[:], uint32(dataDescriptorSignature))366	if bytes.Contains(b, sig[:]) {367		t.Error("there should be no data descriptor")368	}369}370371func TestWriterCopy(t *testing.T) {372	// make a zip file373	buf := new(bytes.Buffer)374	w := NewWriter(buf)375	for _, wt := range writeTests {376		testCreate(t, w, &wt)377	}378	if err := w.Close(); err != nil {379		t.Fatal(err)380	}381382	// read it back383	src, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))384	if err != nil {385		t.Fatal(err)386	}387	for i, wt := range writeTests {388		testReadFile(t, src.File[i], &wt)389	}390391	// make a new zip file copying the old compressed data.392	buf2 := new(bytes.Buffer)393	dst := NewWriter(buf2)394	for _, f := range src.File {395		if err := dst.Copy(f); err != nil {396			t.Fatal(err)397		}398	}399	if err := dst.Close(); err != nil {400		t.Fatal(err)401	}402403	// read the new one back404	r, err := NewReader(bytes.NewReader(buf2.Bytes()), int64(buf2.Len()))405	if err != nil {406		t.Fatal(err)407	}408	for i, wt := range writeTests {409		testReadFile(t, r.File[i], &wt)410	}411}412413func TestWriterCreateRaw(t *testing.T) {414	files := []struct {415		name             string416		content          []byte417		method           uint16418		flags            uint16419		crc32            uint32420		uncompressedSize uint64421		compressedSize   uint64422	}{423		{424			name:    "small store w desc",425			content: []byte("gophers"),426			method:  Store,427			flags:   0x8,428		},429		{430			name:    "small deflate wo desc",431			content: bytes.Repeat([]byte("abcdefg"), 2048),432			method:  Deflate,433		},434	}435436	// write a zip file437	archive := new(bytes.Buffer)438	w := NewWriter(archive)439440	for i := range files {441		f := &files[i]442		f.crc32 = crc32.ChecksumIEEE(f.content)443		size := uint64(len(f.content))444		f.uncompressedSize = size445		f.compressedSize = size446447		var compressedContent []byte448		if f.method == Deflate {449			var buf bytes.Buffer450			w, err := flate.NewWriter(&buf, flate.BestSpeed)451			if err != nil {452				t.Fatalf("flate.NewWriter err = %v", err)453			}454			_, err = w.Write(f.content)455			if err != nil {456				t.Fatalf("flate Write err = %v", err)457			}458			err = w.Close()459			if err != nil {460				t.Fatalf("flate Writer.Close err = %v", err)461			}462			compressedContent = buf.Bytes()463			f.compressedSize = uint64(len(compressedContent))464		}465466		h := &FileHeader{467			Name:               f.name,468			Method:             f.method,469			Flags:              f.flags,470			CRC32:              f.crc32,471			CompressedSize64:   f.compressedSize,472			UncompressedSize64: f.uncompressedSize,473		}474		w, err := w.CreateRaw(h)475		if err != nil {476			t.Fatal(err)477		}478		if compressedContent != nil {479			_, err = w.Write(compressedContent)480		} else {481			_, err = w.Write(f.content)482		}483		if err != nil {484			t.Fatalf("%s Write got %v; want nil", f.name, err)485		}486	}487488	if err := w.Close(); err != nil {489		t.Fatal(err)490	}491492	// read it back493	r, err := NewReader(bytes.NewReader(archive.Bytes()), int64(archive.Len()))494	if err != nil {495		t.Fatal(err)496	}497	for i, want := range files {498		got := r.File[i]499		if got.Name != want.name {500			t.Errorf("got Name %s; want %s", got.Name, want.name)501		}502		if got.Method != want.method {503			t.Errorf("%s: got Method %#x; want %#x", want.name, got.Method, want.method)504		}505		if got.Flags != want.flags {506			t.Errorf("%s: got Flags %#x; want %#x", want.name, got.Flags, want.flags)507		}508		if got.CRC32 != want.crc32 {509			t.Errorf("%s: got CRC32 %#x; want %#x", want.name, got.CRC32, want.crc32)510		}511		if got.CompressedSize64 != want.compressedSize {512			t.Errorf("%s: got CompressedSize64 %d; want %d", want.name, got.CompressedSize64, want.compressedSize)513		}514		if got.UncompressedSize64 != want.uncompressedSize {515			t.Errorf("%s: got UncompressedSize64 %d; want %d", want.name, got.UncompressedSize64, want.uncompressedSize)516		}517518		r, err := got.Open()519		if err != nil {520			t.Errorf("%s: Open err = %v", got.Name, err)521			continue522		}523524		buf, err := io.ReadAll(r)525		if err != nil {526			t.Errorf("%s: ReadAll err = %v", got.Name, err)527			continue528		}529530		if !bytes.Equal(buf, want.content) {531			t.Errorf("%v: ReadAll returned unexpected bytes", got.Name)532		}533	}534}535536func testCreate(t *testing.T, w *Writer, wt *WriteTest) {537	header := &FileHeader{538		Name:   wt.Name,539		Method: wt.Method,540	}541	if wt.Mode != 0 {542		header.SetMode(wt.Mode)543	}544	f, err := w.CreateHeader(header)545	if err != nil {546		t.Fatal(err)547	}548	_, err = f.Write(wt.Data)549	if err != nil {550		t.Fatal(err)551	}552}553554func testReadFile(t *testing.T, f *File, wt *WriteTest) {555	if f.Name != wt.Name {556		t.Fatalf("File name: got %q, want %q", f.Name, wt.Name)557	}558	testFileMode(t, f, wt.Mode)559	rc, err := f.Open()560	if err != nil {561		t.Fatalf("opening %s: %v", f.Name, err)562	}563	b, err := io.ReadAll(rc)564	if err != nil {565		t.Fatalf("reading %s: %v", f.Name, err)566	}567	err = rc.Close()568	if err != nil {569		t.Fatalf("closing %s: %v", f.Name, err)570	}571	if !bytes.Equal(b, wt.Data) {572		t.Errorf("File contents %q, want %q", b, wt.Data)573	}574}575576func BenchmarkCompressedZipGarbage(b *testing.B) {577	bigBuf := bytes.Repeat([]byte("a"), 1<<20)578579	runOnce := func(buf *bytes.Buffer) {580		buf.Reset()581		zw := NewWriter(buf)582		for j := 0; j < 3; j++ {583			w, _ := zw.CreateHeader(&FileHeader{584				Name:   "foo",585				Method: Deflate,586			})587			w.Write(bigBuf)588		}589		zw.Close()590	}591592	b.ReportAllocs()593	// Run once and then reset the timer.594	// This effectively discards the very large initial flate setup cost,595	// as well as the initialization of bigBuf.596	runOnce(&bytes.Buffer{})597	b.ResetTimer()598599	b.RunParallel(func(pb *testing.PB) {600		var buf bytes.Buffer601		for pb.Next() {602			runOnce(&buf)603		}604	})605}606607func writeTestsToFS(tests []WriteTest) fs.FS {608	fsys := fstest.MapFS{}609	for _, wt := range tests {610		fsys[wt.Name] = &fstest.MapFile{611			Data: wt.Data,612			Mode: wt.Mode,613		}614	}615	return fsys616}617618func TestWriterAddFS(t *testing.T) {619	buf := new(bytes.Buffer)620	w := NewWriter(buf)621	tests := []WriteTest{622		{Name: "emptyfolder", Mode: 0o755 | os.ModeDir},623		{Name: "file.go", Data: []byte("hello"), Mode: 0644},624		{Name: "subfolder/another.go", Data: []byte("world"), Mode: 0644},625		// Notably missing here is the "subfolder" directory. This makes sure even626		// if we don't have a subfolder directory listed.627	}628	err := w.AddFS(writeTestsToFS(tests))629	if err != nil {630		t.Fatal(err)631	}632	if err := w.Close(); err != nil {633		t.Fatal(err)634	}635636	// Add subfolder into fsys to match what we'll read from the zip.637	tests = append(tests[:2:2], WriteTest{Name: "subfolder", Mode: 0o555 | os.ModeDir}, tests[2])638639	// read it back640	r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))641	if err != nil {642		t.Fatal(err)643	}644	for i, wt := range tests {645		if wt.Mode.IsDir() {646			wt.Name += "/"647		}648		testReadFile(t, r.File[i], &wt)649	}650}651652func TestIssue61875(t *testing.T) {653	buf := new(bytes.Buffer)654	w := NewWriter(buf)655	tests := []WriteTest{656		{657			Name:   "symlink",658			Data:   []byte("../link/target"),659			Method: Deflate,660			Mode:   0755 | fs.ModeSymlink,661		},662		{663			Name:   "device",664			Data:   []byte(""),665			Method: Deflate,666			Mode:   0755 | fs.ModeDevice,667		},668	}669	err := w.AddFS(writeTestsToFS(tests))670	if err == nil {671		t.Errorf("expected error, got nil")672	}673}

Code quality findings 12

Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer func() {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, wt := range writeTests {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, test := range utf8Tests {
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("%x\n%x\n", got, want)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, wt := range writeTests {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, wt := range writeTests {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, wt := range writeTests {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
content: bytes.Repeat([]byte("abcdefg"), 2048),
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, want := range files {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
bigBuf := bytes.Repeat([]byte("a"), 1<<20)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
tests = append(tests[:2:2], WriteTest{Name: "subfolder", Mode: 0o555 | os.ModeDir}, tests[2])
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, wt := range tests {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.