github.com/bodgit/sevenzip@v1.5.1/reader_test.go (about)

     1  package sevenzip_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"hash"
     7  	"hash/crc32"
     8  	"io"
     9  	"path/filepath"
    10  	"runtime"
    11  	"testing"
    12  	"testing/fstest"
    13  	"testing/iotest"
    14  
    15  	"github.com/bodgit/sevenzip"
    16  	"github.com/bodgit/sevenzip/internal/util"
    17  	"github.com/stretchr/testify/assert"
    18  	"golang.org/x/sync/errgroup"
    19  )
    20  
    21  func reader(r io.Reader) io.Reader {
    22  	return r
    23  }
    24  
    25  func extractFile(tb testing.TB, r io.Reader, h hash.Hash, f *sevenzip.File) error {
    26  	tb.Helper()
    27  
    28  	h.Reset()
    29  
    30  	if _, err := io.Copy(h, r); err != nil {
    31  		return err
    32  	}
    33  
    34  	if f.UncompressedSize > 0 && f.CRC32 == 0 {
    35  		tb.Log("archive member", f.Name, "has no CRC")
    36  
    37  		return nil
    38  	}
    39  
    40  	if !util.CRC32Equal(h.Sum(nil), f.CRC32) {
    41  		return errors.New("CRC doesn't match")
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  //nolint:lll
    48  func extractArchive(tb testing.TB, r *sevenzip.ReadCloser, stream int, h hash.Hash, fn func(io.Reader) io.Reader, optimised bool) error {
    49  	tb.Helper()
    50  
    51  	for _, f := range r.File {
    52  		if stream >= 0 && f.Stream != stream {
    53  			continue
    54  		}
    55  
    56  		rc, err := f.Open()
    57  		if err != nil {
    58  			return err
    59  		}
    60  		defer rc.Close()
    61  
    62  		if err = extractFile(tb, fn(rc), h, f); err != nil {
    63  			return err
    64  		}
    65  
    66  		if optimised {
    67  			rc.Close()
    68  		}
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  //nolint:funlen
    75  func TestOpenReader(t *testing.T) {
    76  	t.Parallel()
    77  
    78  	tables := []struct {
    79  		name, file string
    80  		volumes    []string
    81  		err        error
    82  	}{
    83  		{
    84  			name: "no header compression",
    85  			file: "t0.7z",
    86  		},
    87  		{
    88  			name: "with header compression",
    89  			file: "t1.7z",
    90  		},
    91  		{
    92  			name: "multiple volume",
    93  			file: "multi.7z.001",
    94  			volumes: []string{
    95  				"multi.7z.001",
    96  				"multi.7z.002",
    97  				"multi.7z.003",
    98  				"multi.7z.004",
    99  				"multi.7z.005",
   100  				"multi.7z.006",
   101  			},
   102  		},
   103  		{
   104  			name: "empty streams and files",
   105  			file: "empty.7z",
   106  		},
   107  		{
   108  			name: "bcj2",
   109  			file: "bcj2.7z",
   110  		},
   111  		{
   112  			name: "bzip2",
   113  			file: "bzip2.7z",
   114  		},
   115  		{
   116  			name: "copy",
   117  			file: "copy.7z",
   118  		},
   119  		{
   120  			name: "deflate",
   121  			file: "deflate.7z",
   122  		},
   123  		{
   124  			name: "delta",
   125  			file: "delta.7z",
   126  		},
   127  		{
   128  			name: "lzma",
   129  			file: "lzma.7z",
   130  		},
   131  		{
   132  			name: "lzma2",
   133  			file: "lzma2.7z",
   134  		},
   135  		{
   136  			name: "complex",
   137  			file: "lzma1900.7z",
   138  		},
   139  		{
   140  			name: "lz4",
   141  			file: "lz4.7z",
   142  		},
   143  		{
   144  			name: "brotli",
   145  			file: "brotli.7z",
   146  		},
   147  		{
   148  			name: "zstd",
   149  			file: "zstd.7z",
   150  		},
   151  		{
   152  			name: "sfx",
   153  			file: "sfx.exe",
   154  		},
   155  		{
   156  			name: "bcj",
   157  			file: "bcj.7z",
   158  		},
   159  		{
   160  			name: "ppc",
   161  			file: "ppc.7z",
   162  		},
   163  		{
   164  			name: "arm",
   165  			file: "arm.7z",
   166  		},
   167  		{
   168  			name: "sparc",
   169  			file: "sparc.7z",
   170  		},
   171  		{
   172  			name: "issue 87",
   173  			file: "issue87.7z",
   174  		},
   175  		{
   176  			name: "issue 112",
   177  			file: "file_and_empty.7z",
   178  		},
   179  		{
   180  			name: "issue 113",
   181  			file: "COMPRESS-492.7z",
   182  			err:  sevenzip.ErrMissingUnpackInfo,
   183  		},
   184  	}
   185  
   186  	for _, table := range tables {
   187  		table := table
   188  
   189  		t.Run(table.name, func(t *testing.T) {
   190  			t.Parallel()
   191  
   192  			r, err := sevenzip.OpenReader(filepath.Join("testdata", table.file))
   193  			if err != nil {
   194  				assert.ErrorIs(t, err, table.err)
   195  
   196  				return
   197  			}
   198  			defer r.Close()
   199  
   200  			volumes := []string{}
   201  
   202  			if table.volumes != nil {
   203  				for _, v := range table.volumes {
   204  					volumes = append(volumes, filepath.Join("testdata", v))
   205  				}
   206  			} else {
   207  				volumes = append(volumes, filepath.Join("testdata", table.file))
   208  			}
   209  
   210  			assert.Equal(t, volumes, r.Volumes())
   211  
   212  			if err := extractArchive(t, r, -1, crc32.NewIEEE(), iotest.OneByteReader, true); err != nil {
   213  				t.Fatal(err)
   214  			}
   215  		})
   216  	}
   217  }
   218  
   219  func TestOpenReaderWithPassword(t *testing.T) {
   220  	t.Parallel()
   221  
   222  	tables := []struct {
   223  		name, file, password string
   224  	}{
   225  		{
   226  			name:     "no header compression",
   227  			file:     "t2.7z",
   228  			password: "password",
   229  		},
   230  		{
   231  			name:     "with header compression",
   232  			file:     "t3.7z",
   233  			password: "password",
   234  		},
   235  		{
   236  			name:     "issue 75",
   237  			file:     "7zcracker.7z",
   238  			password: "876",
   239  		},
   240  	}
   241  
   242  	for _, table := range tables {
   243  		table := table
   244  
   245  		t.Run(table.name, func(t *testing.T) {
   246  			t.Parallel()
   247  
   248  			r, err := sevenzip.OpenReaderWithPassword(filepath.Join("testdata", table.file), table.password)
   249  			if err != nil {
   250  				t.Fatal(err)
   251  			}
   252  			defer r.Close()
   253  
   254  			if err := extractArchive(t, r, -1, crc32.NewIEEE(), iotest.OneByteReader, true); err != nil {
   255  				t.Fatal(err)
   256  			}
   257  		})
   258  	}
   259  }
   260  
   261  func TestFS(t *testing.T) {
   262  	t.Parallel()
   263  
   264  	r, err := sevenzip.OpenReader(filepath.Join("testdata", "lzma1900.7z"))
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	defer r.Close()
   269  
   270  	if err := fstest.TestFS(r, "Asm/arm/7zCrcOpt.asm", "bin/x64/7zr.exe"); err != nil {
   271  		t.Fatal(err)
   272  	}
   273  }
   274  
   275  func ExampleOpenReader() {
   276  	r, err := sevenzip.OpenReader(filepath.Join("testdata", "multi.7z.001"))
   277  	if err != nil {
   278  		panic(err)
   279  	}
   280  	defer r.Close()
   281  
   282  	for _, file := range r.File {
   283  		fmt.Println(file.Name)
   284  	}
   285  	// Output: 01
   286  	// 02
   287  	// 03
   288  	// 04
   289  	// 05
   290  	// 06
   291  	// 07
   292  	// 08
   293  	// 09
   294  	// 10
   295  }
   296  
   297  func benchmarkArchiveParallel(b *testing.B, file string) {
   298  	b.Helper()
   299  
   300  	for n := 0; n < b.N; n++ {
   301  		r, err := sevenzip.OpenReader(filepath.Join("testdata", file))
   302  		if err != nil {
   303  			b.Fatal(err)
   304  		}
   305  		defer r.Close()
   306  
   307  		streams := make(map[int]struct{}, len(r.File))
   308  
   309  		for _, f := range r.File {
   310  			streams[f.Stream] = struct{}{}
   311  		}
   312  
   313  		eg := new(errgroup.Group)
   314  		eg.SetLimit(runtime.NumCPU())
   315  
   316  		for stream := range streams {
   317  			stream := stream
   318  
   319  			eg.Go(func() error {
   320  				return extractArchive(b, r, stream, crc32.NewIEEE(), reader, true)
   321  			})
   322  		}
   323  
   324  		if err := eg.Wait(); err != nil {
   325  			b.Fatal(err)
   326  		}
   327  
   328  		r.Close()
   329  	}
   330  }
   331  
   332  func benchmarkArchiveNaiveParallel(b *testing.B, file string, workers int) {
   333  	b.Helper()
   334  
   335  	for n := 0; n < b.N; n++ {
   336  		r, err := sevenzip.OpenReader(filepath.Join("testdata", file))
   337  		if err != nil {
   338  			b.Fatal(err)
   339  		}
   340  		defer r.Close()
   341  
   342  		eg := new(errgroup.Group)
   343  		eg.SetLimit(workers)
   344  
   345  		for _, f := range r.File {
   346  			f := f
   347  
   348  			eg.Go(func() error {
   349  				rc, err := f.Open()
   350  				if err != nil {
   351  					return err
   352  				}
   353  				defer rc.Close()
   354  
   355  				return extractFile(b, rc, crc32.NewIEEE(), f)
   356  			})
   357  		}
   358  
   359  		if err := eg.Wait(); err != nil {
   360  			b.Fatal(err)
   361  		}
   362  
   363  		r.Close()
   364  	}
   365  }
   366  
   367  func benchmarkArchive(b *testing.B, file, password string, optimised bool) {
   368  	b.Helper()
   369  
   370  	h := crc32.NewIEEE()
   371  
   372  	for n := 0; n < b.N; n++ {
   373  		r, err := sevenzip.OpenReaderWithPassword(filepath.Join("testdata", file), password)
   374  		if err != nil {
   375  			b.Fatal(err)
   376  		}
   377  		defer r.Close()
   378  
   379  		if err := extractArchive(b, r, -1, h, reader, optimised); err != nil {
   380  			b.Fatal(err)
   381  		}
   382  
   383  		r.Close()
   384  	}
   385  }
   386  
   387  func BenchmarkAES7z(b *testing.B) {
   388  	benchmarkArchive(b, "aes7z.7z", "password", true)
   389  }
   390  
   391  func BenchmarkBzip2(b *testing.B) {
   392  	benchmarkArchive(b, "bzip2.7z", "", true)
   393  }
   394  
   395  func BenchmarkCopy(b *testing.B) {
   396  	benchmarkArchive(b, "copy.7z", "", true)
   397  }
   398  
   399  func BenchmarkDeflate(b *testing.B) {
   400  	benchmarkArchive(b, "deflate.7z", "", true)
   401  }
   402  
   403  func BenchmarkDelta(b *testing.B) {
   404  	benchmarkArchive(b, "delta.7z", "", true)
   405  }
   406  
   407  func BenchmarkLZMA(b *testing.B) {
   408  	benchmarkArchive(b, "lzma.7z", "", true)
   409  }
   410  
   411  func BenchmarkLZMA2(b *testing.B) {
   412  	benchmarkArchive(b, "lzma2.7z", "", true)
   413  }
   414  
   415  func BenchmarkBCJ2(b *testing.B) {
   416  	benchmarkArchive(b, "bcj2.7z", "", true)
   417  }
   418  
   419  func BenchmarkComplex(b *testing.B) {
   420  	benchmarkArchive(b, "lzma1900.7z", "", true)
   421  }
   422  
   423  func BenchmarkLZ4(b *testing.B) {
   424  	benchmarkArchive(b, "lz4.7z", "", true)
   425  }
   426  
   427  func BenchmarkBrotli(b *testing.B) {
   428  	benchmarkArchive(b, "brotli.7z", "", true)
   429  }
   430  
   431  func BenchmarkZstandard(b *testing.B) {
   432  	benchmarkArchive(b, "zstd.7z", "", true)
   433  }
   434  
   435  func BenchmarkNaiveReader(b *testing.B) {
   436  	benchmarkArchive(b, "lzma1900.7z", "", false)
   437  }
   438  
   439  func BenchmarkOptimisedReader(b *testing.B) {
   440  	benchmarkArchive(b, "lzma1900.7z", "", true)
   441  }
   442  
   443  func BenchmarkNaiveParallelReader(b *testing.B) {
   444  	benchmarkArchiveNaiveParallel(b, "lzma1900.7z", runtime.NumCPU())
   445  }
   446  
   447  func BenchmarkNaiveSingleParallelReader(b *testing.B) {
   448  	benchmarkArchiveNaiveParallel(b, "lzma1900.7z", 1)
   449  }
   450  
   451  func BenchmarkParallelReader(b *testing.B) {
   452  	benchmarkArchiveParallel(b, "lzma1900.7z")
   453  }
   454  
   455  func BenchmarkBCJ(b *testing.B) {
   456  	benchmarkArchive(b, "bcj.7z", "", true)
   457  }
   458  
   459  func BenchmarkPPC(b *testing.B) {
   460  	benchmarkArchive(b, "ppc.7z", "", true)
   461  }
   462  
   463  func BenchmarkARM(b *testing.B) {
   464  	benchmarkArchive(b, "arm.7z", "", true)
   465  }
   466  
   467  func BenchmarkSPARC(b *testing.B) {
   468  	benchmarkArchive(b, "sparc.7z", "", true)
   469  }