deb_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package deb
  4. import (
  5. "bytes"
  6. "crypto/md5"
  7. "crypto/sha1"
  8. "crypto/sha256"
  9. "encoding/hex"
  10. "fmt"
  11. "hash"
  12. "strings"
  13. "testing"
  14. "github.com/google/go-cmp/cmp"
  15. "github.com/goreleaser/nfpm/v2"
  16. _ "github.com/goreleaser/nfpm/v2/deb"
  17. )
  18. func TestDebInfo(t *testing.T) {
  19. tests := []struct {
  20. name string
  21. in []byte
  22. want *Info
  23. wantErr bool
  24. }{
  25. {
  26. name: "simple",
  27. in: mkTestDeb("1.2.3", "amd64"),
  28. want: &Info{
  29. Version: "1.2.3",
  30. Arch: "amd64",
  31. Control: mkControl(
  32. "Package", "tailscale",
  33. "Version", "1.2.3",
  34. "Section", "net",
  35. "Priority", "extra",
  36. "Architecture", "amd64",
  37. "Maintainer", "Tail Scalar",
  38. "Installed-Size", "0",
  39. "Description", "test package"),
  40. },
  41. },
  42. {
  43. name: "arm64",
  44. in: mkTestDeb("1.2.3", "arm64"),
  45. want: &Info{
  46. Version: "1.2.3",
  47. Arch: "arm64",
  48. Control: mkControl(
  49. "Package", "tailscale",
  50. "Version", "1.2.3",
  51. "Section", "net",
  52. "Priority", "extra",
  53. "Architecture", "arm64",
  54. "Maintainer", "Tail Scalar",
  55. "Installed-Size", "0",
  56. "Description", "test package"),
  57. },
  58. },
  59. {
  60. name: "unstable",
  61. in: mkTestDeb("1.7.25", "amd64"),
  62. want: &Info{
  63. Version: "1.7.25",
  64. Arch: "amd64",
  65. Control: mkControl(
  66. "Package", "tailscale",
  67. "Version", "1.7.25",
  68. "Section", "net",
  69. "Priority", "extra",
  70. "Architecture", "amd64",
  71. "Maintainer", "Tail Scalar",
  72. "Installed-Size", "0",
  73. "Description", "test package"),
  74. },
  75. },
  76. // These truncation tests assume the structure of a .deb
  77. // package, which is as follows:
  78. // magic: 8 bytes
  79. // file header: 60 bytes, before each file blob
  80. //
  81. // The first file in a .deb ar is "debian-binary", which is 4
  82. // bytes long and consists of "2.0\n".
  83. // The second file is control.tar.gz, which is what we care
  84. // about introspecting for metadata.
  85. // The final file is data.tar.gz, which we don't care about.
  86. //
  87. // The first file in control.tar.gz is the "control" file we
  88. // want to read for metadata.
  89. {
  90. name: "truncated_ar_magic",
  91. in: mkTestDeb("1.7.25", "amd64")[:4],
  92. wantErr: true,
  93. },
  94. {
  95. name: "truncated_ar_header",
  96. in: mkTestDeb("1.7.25", "amd64")[:30],
  97. wantErr: true,
  98. },
  99. {
  100. name: "missing_control_tgz",
  101. // Truncate right after the "debian-binary" file, which
  102. // makes the file a valid 1-file archive that's missing
  103. // control.tar.gz.
  104. in: mkTestDeb("1.7.25", "amd64")[:72],
  105. wantErr: true,
  106. },
  107. {
  108. name: "truncated_tgz",
  109. in: mkTestDeb("1.7.25", "amd64")[:172],
  110. wantErr: true,
  111. },
  112. }
  113. for _, test := range tests {
  114. // mkTestDeb returns non-deterministic output due to
  115. // timestamps embedded in the package file, so compute the
  116. // wanted hashes on the fly here.
  117. if test.want != nil {
  118. test.want.MD5 = mkHash(test.in, md5.New)
  119. test.want.SHA1 = mkHash(test.in, sha1.New)
  120. test.want.SHA256 = mkHash(test.in, sha256.New)
  121. }
  122. t.Run(test.name, func(t *testing.T) {
  123. b := bytes.NewBuffer(test.in)
  124. got, err := Read(b)
  125. if err != nil {
  126. if test.wantErr {
  127. t.Logf("got expected error: %v", err)
  128. return
  129. }
  130. t.Fatalf("reading deb info: %v", err)
  131. }
  132. if diff := diff(got, test.want); diff != "" {
  133. t.Fatalf("parsed info diff (-got+want):\n%s", diff)
  134. }
  135. })
  136. }
  137. }
  138. func diff(got, want any) string {
  139. matchField := func(name string) func(p cmp.Path) bool {
  140. return func(p cmp.Path) bool {
  141. if len(p) != 3 {
  142. return false
  143. }
  144. return p[2].String() == "."+name
  145. }
  146. }
  147. toLines := cmp.Transformer("lines", func(b []byte) []string { return strings.Split(string(b), "\n") })
  148. toHex := cmp.Transformer("hex", func(b []byte) string { return hex.EncodeToString(b) })
  149. return cmp.Diff(got, want,
  150. cmp.FilterPath(matchField("Control"), toLines),
  151. cmp.FilterPath(matchField("MD5"), toHex),
  152. cmp.FilterPath(matchField("SHA1"), toHex),
  153. cmp.FilterPath(matchField("SHA256"), toHex))
  154. }
  155. func mkTestDeb(version, arch string) []byte {
  156. info := nfpm.WithDefaults(&nfpm.Info{
  157. Name: "tailscale",
  158. Description: "test package",
  159. Arch: arch,
  160. Platform: "linux",
  161. Version: version,
  162. Section: "net",
  163. Priority: "extra",
  164. Maintainer: "Tail Scalar",
  165. })
  166. pkg, err := nfpm.Get("deb")
  167. if err != nil {
  168. panic(fmt.Sprintf("getting deb packager: %v", err))
  169. }
  170. var b bytes.Buffer
  171. if err := pkg.Package(info, &b); err != nil {
  172. panic(fmt.Sprintf("creating deb package: %v", err))
  173. }
  174. return b.Bytes()
  175. }
  176. func mkControl(fs ...string) []byte {
  177. if len(fs)%2 != 0 {
  178. panic("odd number of control file fields")
  179. }
  180. var b bytes.Buffer
  181. for i := 0; i < len(fs); i = i + 2 {
  182. k, v := fs[i], fs[i+1]
  183. fmt.Fprintf(&b, "%s: %s\n", k, v)
  184. }
  185. return bytes.TrimSpace(b.Bytes())
  186. }
  187. func mkHash(b []byte, hasher func() hash.Hash) []byte {
  188. h := hasher()
  189. h.Write(b)
  190. return h.Sum(nil)
  191. }