rootfs_test.go (5651B) - Raw
1 package rootfs 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "encoding/json" 7 "reflect" 8 "testing" 9 10 "git.jakstys.lt/motiejus/undocker/rootfs/internal/tartest" 11 ) 12 13 type ( 14 file = tartest.File 15 dir = tartest.Dir 16 hardlink = tartest.Hardlink 17 extractable = tartest.Extractable 18 tarball = tartest.Tarball 19 ) 20 21 func TestRootFS(t *testing.T) { 22 layer0 := tarball{ 23 dir{Name: "/", UID: 0}, 24 file{Name: "/file", UID: 0, Contents: bytes.NewBufferString("from 0")}, 25 } 26 27 layer1 := tarball{ 28 file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")}, 29 } 30 31 layer2 := tarball{ 32 dir{Name: "/", UID: 2}, 33 } 34 35 tests := []struct { 36 name string 37 image tarball 38 want []extractable 39 wantErr string 40 }{ 41 { 42 name: "empty tarball", 43 image: tarball{manifest{}}, 44 want: []extractable{}, 45 }, 46 { 47 name: "no manifest", 48 image: tarball{}, 49 wantErr: "empty or missing manifest", 50 }, 51 { 52 name: "missing layer", 53 image: tarball{manifest{"layer0/layer.tar"}}, 54 wantErr: "layer0/layer.tar defined in manifest, missing in tarball", 55 }, 56 { 57 name: "basic file overwrite, layer order mixed", 58 image: tarball{ 59 file{Name: "layer1/layer.tar", Contents: layer1.Buffer()}, 60 file{Name: "layer0/layer.tar", Contents: layer0.Buffer()}, 61 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 62 }, 63 want: []extractable{ 64 dir{Name: "/", UID: 0}, 65 file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")}, 66 }, 67 }, 68 { 69 name: "overwrite file with hardlink", 70 image: tarball{ 71 file{Name: "layer0/layer.tar", Contents: tarball{ 72 file{Name: "a"}, 73 }.Buffer()}, 74 file{Name: "layer1/layer.tar", Contents: tarball{ 75 hardlink{Name: "a"}, 76 }.Buffer()}, 77 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 78 }, 79 want: []extractable{ 80 hardlink{Name: "a"}, 81 }, 82 }, 83 { 84 name: "directory overwrite retains original dir", 85 image: tarball{ 86 file{Name: "layer2/layer.tar", Contents: layer2.Buffer()}, 87 file{Name: "layer0/layer.tar", Contents: layer0.Buffer()}, 88 file{Name: "layer1/layer.tar", Contents: layer1.Buffer()}, 89 manifest{"layer0/layer.tar", "layer1/layer.tar", "layer2/layer.tar"}, 90 }, 91 want: []extractable{ 92 dir{Name: "/", UID: 0}, 93 file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")}, 94 dir{Name: "/", UID: 2}, 95 }, 96 }, 97 { 98 name: "simple whiteout", 99 image: tarball{ 100 file{Name: "layer0/layer.tar", Contents: tarball{ 101 file{Name: "filea"}, 102 file{Name: "fileb"}, 103 dir{Name: "dira"}, 104 dir{Name: "dirb"}, 105 }.Buffer()}, 106 file{Name: "layer1/layer.tar", Contents: tarball{ 107 hardlink{Name: ".wh.filea"}, 108 hardlink{Name: ".wh.dira"}, 109 }.Buffer()}, 110 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 111 }, 112 want: []extractable{ 113 file{Name: "fileb"}, 114 dir{Name: "dirb"}, 115 }, 116 }, 117 { 118 name: "whiteout with override", 119 image: tarball{ 120 file{Name: "layer0/layer.tar", Contents: tarball{ 121 file{Name: "file", Contents: bytes.NewBufferString("from 0")}, 122 }.Buffer()}, 123 file{Name: "layer1/layer.tar", Contents: tarball{ 124 hardlink{Name: ".wh.file"}, 125 }.Buffer()}, 126 file{Name: "layer2/layer.tar", Contents: tarball{ 127 file{Name: "file", Contents: bytes.NewBufferString("from 3")}, 128 }.Buffer()}, 129 manifest{ 130 "layer0/layer.tar", 131 "layer1/layer.tar", 132 "layer2/layer.tar", 133 }, 134 }, 135 want: []extractable{ 136 file{Name: "file", Contents: bytes.NewBufferString("from 3")}, 137 }, 138 }, 139 { 140 name: "directories do not whiteout", 141 image: tarball{ 142 file{Name: "layer0/layer.tar", Contents: tarball{ 143 dir{Name: "dir"}, 144 }.Buffer()}, 145 file{Name: "layer1/layer.tar", Contents: tarball{ 146 dir{Name: ".wh.dir"}, 147 }.Buffer()}, 148 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 149 }, 150 want: []extractable{ 151 dir{Name: "dir"}, 152 dir{Name: ".wh.dir"}, 153 }, 154 }, 155 { 156 name: "simple readdir whiteout", 157 image: tarball{ 158 file{Name: "layer0/layer.tar", Contents: tarball{ 159 dir{Name: "a"}, 160 file{Name: "a/filea"}, 161 }.Buffer()}, 162 file{Name: "layer1/layer.tar", Contents: tarball{ 163 dir{Name: "a"}, 164 file{Name: "a/fileb"}, 165 hardlink{Name: "a/.wh..wh..opq"}, 166 }.Buffer()}, 167 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 168 }, 169 want: []extractable{ 170 dir{Name: "a"}, 171 file{Name: "a/fileb"}, 172 }, 173 }, 174 { 175 name: "archived layer", 176 image: tarball{ 177 file{Name: "layer1/layer.tar", Contents: layer1.Gzip()}, 178 file{Name: "layer0/layer.tar", Contents: layer0.Gzip()}, 179 manifest{"layer0/layer.tar", "layer1/layer.tar"}, 180 }, 181 want: []extractable{ 182 dir{Name: "/", UID: 0}, 183 file{Name: "/file", UID: 1, Contents: bytes.NewBufferString("from 1")}, 184 }, 185 }, 186 } 187 188 for _, tt := range tests { 189 t.Run(tt.name, func(t *testing.T) { 190 in := bytes.NewReader(tt.image.Buffer().Bytes()) 191 out := bytes.Buffer{} 192 193 err := Flatten(in, &out) 194 if tt.wantErr != "" { 195 if err == nil { 196 t.Fatal("expected error, got nil") 197 } 198 if tt.wantErr != err.Error() { 199 t.Errorf("want != got: %s != %s", tt.wantErr, err.Error()) 200 } 201 return 202 } 203 outb := out.Bytes() 204 if err != nil { 205 t.Fatal("expected error, got nil") 206 } 207 got := tartest.Extract(t, bytes.NewReader(outb)) 208 if !reflect.DeepEqual(tt.want, got) { 209 t.Errorf("want != got: %v != %v", tt.want, got) 210 } 211 }) 212 } 213 } 214 215 // Helpers 216 type manifest []string 217 218 func (m manifest) Tar(tw *tar.Writer) error { 219 b, err := json.Marshal(dockerManifestJSON{{Layers: m}}) 220 if err != nil { 221 return err 222 } 223 return file{ 224 Name: "manifest.json", 225 Contents: bytes.NewBuffer(b), 226 }.Tar(tw) 227 }