package archive // import "github.com/docker/docker/pkg/archive" import ( "archive/tar" "fmt" "io" "os" "path" "sort" "testing" "github.com/docker/docker/pkg/idtools" ) func TestHardLinkOrder(t *testing.T) { names := []string{"file1.txt", "file2.txt", "file3.txt"} msg := []byte("Hey y'all") // Create dir src, err := os.MkdirTemp("", "docker-hardlink-test-src-") if err != nil { t.Fatal(err) } defer os.RemoveAll(src) for _, name := range names { func() { fh, err := os.Create(path.Join(src, name)) if err != nil { t.Fatal(err) } defer fh.Close() if _, err = fh.Write(msg); err != nil { t.Fatal(err) } }() } // Create dest, with changes that includes hardlinks dest, err := os.MkdirTemp("", "docker-hardlink-test-dest-") if err != nil { t.Fatal(err) } os.RemoveAll(dest) // we just want the name, at first if err := copyDir(src, dest); err != nil { t.Fatal(err) } defer os.RemoveAll(dest) for _, name := range names { for i := 0; i < 5; i++ { if err := os.Link(path.Join(dest, name), path.Join(dest, fmt.Sprintf("%s.link%d", name, i))); err != nil { t.Fatal(err) } } } // get changes changes, err := ChangesDirs(dest, src) if err != nil { t.Fatal(err) } // sort sort.Sort(changesByPath(changes)) // ExportChanges ar, err := ExportChanges(dest, changes, idtools.IdentityMapping{}) if err != nil { t.Fatal(err) } hdrs, err := walkHeaders(ar) if err != nil { t.Fatal(err) } // reverse sort sort.Sort(sort.Reverse(changesByPath(changes))) // ExportChanges arRev, err := ExportChanges(dest, changes, idtools.IdentityMapping{}) if err != nil { t.Fatal(err) } hdrsRev, err := walkHeaders(arRev) if err != nil { t.Fatal(err) } // line up the two sets sort.Sort(tarHeaders(hdrs)) sort.Sort(tarHeaders(hdrsRev)) // compare Size and LinkName for i := range hdrs { if hdrs[i].Name != hdrsRev[i].Name { t.Errorf("headers - expected name %q; but got %q", hdrs[i].Name, hdrsRev[i].Name) } if hdrs[i].Size != hdrsRev[i].Size { t.Errorf("headers - %q expected size %d; but got %d", hdrs[i].Name, hdrs[i].Size, hdrsRev[i].Size) } if hdrs[i].Typeflag != hdrsRev[i].Typeflag { t.Errorf("headers - %q expected type %d; but got %d", hdrs[i].Name, hdrs[i].Typeflag, hdrsRev[i].Typeflag) } if hdrs[i].Linkname != hdrsRev[i].Linkname { t.Errorf("headers - %q expected linkname %q; but got %q", hdrs[i].Name, hdrs[i].Linkname, hdrsRev[i].Linkname) } } } type tarHeaders []tar.Header func (th tarHeaders) Len() int { return len(th) } func (th tarHeaders) Swap(i, j int) { th[j], th[i] = th[i], th[j] } func (th tarHeaders) Less(i, j int) bool { return th[i].Name < th[j].Name } func walkHeaders(r io.Reader) ([]tar.Header, error) { t := tar.NewReader(r) var headers []tar.Header for { hdr, err := t.Next() if err != nil { if err == io.EOF { break } return headers, err } headers = append(headers, *hdr) } return headers, nil }