aur_install_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "testing"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/Jguer/yay/v11/pkg/db/mock"
  13. "github.com/Jguer/yay/v11/pkg/dep"
  14. "github.com/Jguer/yay/v11/pkg/settings/exe"
  15. "github.com/Jguer/yay/v11/pkg/settings/parser"
  16. "github.com/Jguer/yay/v11/pkg/vcs"
  17. )
  18. func ptrString(s string) *string {
  19. return &s
  20. }
  21. func TestInstaller_InstallNeeded(t *testing.T) {
  22. t.Parallel()
  23. makepkgBin := t.TempDir() + "/makepkg"
  24. pacmanBin := t.TempDir() + "/pacman"
  25. f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755)
  26. require.NoError(t, err)
  27. require.NoError(t, f.Close())
  28. f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755)
  29. require.NoError(t, err)
  30. require.NoError(t, f.Close())
  31. type testCase struct {
  32. desc string
  33. isInstalled bool
  34. isBuilt bool
  35. wantShow []string
  36. wantCapture []string
  37. }
  38. testCases := []testCase{
  39. {
  40. desc: "not installed and not built",
  41. isInstalled: false,
  42. isBuilt: false,
  43. wantShow: []string{
  44. "makepkg --nobuild -fC --ignorearch",
  45. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  46. "pacman -U --needed --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  47. "pacman -D -q --asexplicit --config -- yay",
  48. },
  49. wantCapture: []string{"makepkg --packagelist"},
  50. },
  51. {
  52. desc: "not installed and built",
  53. isInstalled: false,
  54. isBuilt: true,
  55. wantShow: []string{
  56. "makepkg --nobuild -fC --ignorearch",
  57. "makepkg -c --nobuild --noextract --ignorearch",
  58. "pacman -U --needed --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  59. "pacman -D -q --asexplicit --config -- yay",
  60. },
  61. wantCapture: []string{"makepkg --packagelist"},
  62. },
  63. {
  64. desc: "installed",
  65. isInstalled: true,
  66. isBuilt: false,
  67. wantShow: []string{
  68. "makepkg --nobuild -fC --ignorearch",
  69. "makepkg -c --nobuild --noextract --ignorearch",
  70. },
  71. wantCapture: []string{"makepkg --packagelist"},
  72. },
  73. }
  74. for _, tc := range testCases {
  75. tc := tc
  76. t.Run(tc.desc, func(td *testing.T) {
  77. tmpDir := td.TempDir()
  78. pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst"
  79. captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
  80. return pkgTar, "", nil
  81. }
  82. i := 0
  83. showOverride := func(cmd *exec.Cmd) error {
  84. i++
  85. if i == 2 {
  86. if !tc.isBuilt {
  87. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  88. require.NoError(td, err)
  89. require.NoError(td, f.Close())
  90. }
  91. }
  92. return nil
  93. }
  94. // create a mock file
  95. if tc.isBuilt {
  96. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  97. require.NoError(td, err)
  98. require.NoError(td, f.Close())
  99. }
  100. isCorrectInstalledOverride := func(string, string) bool {
  101. return tc.isInstalled
  102. }
  103. mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride}
  104. mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride}
  105. cmdBuilder := &exe.CmdBuilder{
  106. MakepkgBin: makepkgBin,
  107. SudoBin: "su",
  108. PacmanBin: pacmanBin,
  109. Runner: mockRunner,
  110. SudoLoopEnabled: false,
  111. }
  112. cmdBuilder.Runner = mockRunner
  113. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false)
  114. cmdArgs := parser.MakeArguments()
  115. cmdArgs.AddArg("needed")
  116. cmdArgs.AddTarget("yay")
  117. pkgBuildDirs := map[string]string{
  118. "yay": tmpDir,
  119. }
  120. targets := []map[string]*dep.InstallInfo{
  121. {
  122. "yay": {
  123. Source: dep.AUR,
  124. Reason: dep.Explicit,
  125. Version: "91.0.0-1",
  126. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  127. AURBase: ptrString("yay"),
  128. },
  129. },
  130. }
  131. errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs)
  132. require.NoError(td, errI)
  133. require.Len(td, mockRunner.ShowCalls, len(tc.wantShow))
  134. require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture))
  135. for i, call := range mockRunner.ShowCalls {
  136. show := call.Args[0].(*exec.Cmd).String()
  137. show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path
  138. show = strings.ReplaceAll(show, makepkgBin, "makepkg")
  139. show = strings.ReplaceAll(show, pacmanBin, "pacman")
  140. // options are in a different order on different systems and on CI root user is used
  141. assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show)
  142. }
  143. for i, call := range mockRunner.CaptureCalls {
  144. capture := call.Args[0].(*exec.Cmd).String()
  145. capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path
  146. capture = strings.ReplaceAll(capture, makepkgBin, "makepkg")
  147. capture = strings.ReplaceAll(capture, pacmanBin, "pacman")
  148. assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture)
  149. }
  150. })
  151. }
  152. }
  153. func TestInstaller_InstallMixedSourcesAndLayers(t *testing.T) {
  154. t.Parallel()
  155. makepkgBin := t.TempDir() + "/makepkg"
  156. pacmanBin := t.TempDir() + "/pacman"
  157. f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755)
  158. require.NoError(t, err)
  159. require.NoError(t, f.Close())
  160. f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755)
  161. require.NoError(t, err)
  162. require.NoError(t, f.Close())
  163. type testCase struct {
  164. desc string
  165. targets []map[string]*dep.InstallInfo
  166. wantShow []string
  167. wantCapture []string
  168. }
  169. tmpDir := t.TempDir()
  170. tmpDirJfin := t.TempDir()
  171. testCases := []testCase{
  172. {
  173. desc: "same layer -- different sources",
  174. wantShow: []string{
  175. "pacman -S --config /etc/pacman.conf -- core/linux",
  176. "pacman -D -q --asdeps --config /etc/pacman.conf -- linux",
  177. "makepkg --nobuild -fC --ignorearch",
  178. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  179. "pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  180. "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay",
  181. },
  182. wantCapture: []string{"makepkg --packagelist"},
  183. targets: []map[string]*dep.InstallInfo{
  184. {
  185. "yay": {
  186. Source: dep.AUR,
  187. Reason: dep.Explicit,
  188. Version: "91.0.0-1",
  189. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  190. AURBase: ptrString("yay"),
  191. },
  192. "linux": {
  193. Source: dep.Sync,
  194. Reason: dep.Dep,
  195. Version: "17.0.0-1",
  196. SyncDBName: ptrString("core"),
  197. },
  198. },
  199. },
  200. },
  201. {
  202. desc: "different layer -- different sources",
  203. wantShow: []string{
  204. "pacman -S --config /etc/pacman.conf -- core/linux",
  205. "pacman -D -q --asdeps --config /etc/pacman.conf -- linux",
  206. "makepkg --nobuild -fC --ignorearch",
  207. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  208. "pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  209. "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay",
  210. },
  211. wantCapture: []string{"makepkg --packagelist"},
  212. targets: []map[string]*dep.InstallInfo{
  213. {
  214. "yay": {
  215. Source: dep.AUR,
  216. Reason: dep.Explicit,
  217. Version: "91.0.0-1",
  218. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  219. AURBase: ptrString("yay"),
  220. },
  221. }, {
  222. "linux": {
  223. Source: dep.Sync,
  224. Reason: dep.Dep,
  225. Version: "17.0.0-1",
  226. SyncDBName: ptrString("core"),
  227. },
  228. },
  229. },
  230. },
  231. {
  232. desc: "same layer -- sync",
  233. wantShow: []string{
  234. "pacman -S --config /etc/pacman.conf -- extra/linux-zen core/linux",
  235. "pacman -D -q --asexplicit --config /etc/pacman.conf -- linux-zen linux",
  236. },
  237. wantCapture: []string{},
  238. targets: []map[string]*dep.InstallInfo{
  239. {
  240. "linux-zen": {
  241. Source: dep.Sync,
  242. Reason: dep.Explicit,
  243. Version: "18.0.0-1",
  244. SyncDBName: ptrString("extra"),
  245. },
  246. "linux": {
  247. Source: dep.Sync,
  248. Reason: dep.Explicit,
  249. Version: "17.0.0-1",
  250. SyncDBName: ptrString("core"),
  251. },
  252. },
  253. },
  254. },
  255. {
  256. desc: "same layer -- aur",
  257. wantShow: []string{
  258. "makepkg --nobuild -fC --ignorearch",
  259. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  260. "makepkg --nobuild -fC --ignorearch",
  261. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  262. "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  263. "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay",
  264. },
  265. wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist"},
  266. targets: []map[string]*dep.InstallInfo{
  267. {
  268. "yay": {
  269. Source: dep.AUR,
  270. Reason: dep.Explicit,
  271. Version: "91.0.0-1",
  272. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  273. AURBase: ptrString("yay"),
  274. },
  275. "jellyfin-server": {
  276. Source: dep.AUR,
  277. Reason: dep.Explicit,
  278. Version: "10.8.8-1",
  279. SrcinfoPath: ptrString(tmpDirJfin + "/.SRCINFO"),
  280. AURBase: ptrString("jellyfin"),
  281. },
  282. },
  283. },
  284. },
  285. {
  286. desc: "different layer -- aur",
  287. wantShow: []string{
  288. "makepkg --nobuild -fC --ignorearch",
  289. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  290. "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-server-10.8.8-1-x86_64.pkg.tar.zst",
  291. "pacman -D -q --asdeps --config /etc/pacman.conf -- jellyfin-server",
  292. "makepkg --nobuild -fC --ignorearch",
  293. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  294. "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst",
  295. "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay",
  296. },
  297. wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist"},
  298. targets: []map[string]*dep.InstallInfo{
  299. {
  300. "yay": {
  301. Source: dep.AUR,
  302. Reason: dep.Explicit,
  303. Version: "91.0.0-1",
  304. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  305. AURBase: ptrString("yay"),
  306. },
  307. }, {
  308. "jellyfin-server": {
  309. Source: dep.AUR,
  310. Reason: dep.MakeDep,
  311. Version: "10.8.8-1",
  312. SrcinfoPath: ptrString(tmpDirJfin + "/.SRCINFO"),
  313. AURBase: ptrString("jellyfin"),
  314. },
  315. },
  316. },
  317. },
  318. }
  319. for _, tc := range testCases {
  320. tc := tc
  321. t.Run(tc.desc, func(td *testing.T) {
  322. pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst"
  323. jfinPkgTar := tmpDirJfin + "/jellyfin-server-10.8.8-1-x86_64.pkg.tar.zst"
  324. captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
  325. if cmd.Dir == tmpDirJfin {
  326. return jfinPkgTar, "", nil
  327. }
  328. if cmd.Dir == tmpDir {
  329. return pkgTar, "", nil
  330. }
  331. return "", "", fmt.Errorf("unexpected command: %s - %s", cmd.String(), cmd.Dir)
  332. }
  333. showOverride := func(cmd *exec.Cmd) error {
  334. if strings.Contains(cmd.String(), "makepkg -cf --noconfirm") && cmd.Dir == tmpDir {
  335. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  336. require.NoError(td, err)
  337. require.NoError(td, f.Close())
  338. }
  339. if strings.Contains(cmd.String(), "makepkg -cf --noconfirm") && cmd.Dir == tmpDirJfin {
  340. f, err := os.OpenFile(jfinPkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  341. require.NoError(td, err)
  342. require.NoError(td, f.Close())
  343. }
  344. return nil
  345. }
  346. defer os.Remove(pkgTar)
  347. defer os.Remove(jfinPkgTar)
  348. isCorrectInstalledOverride := func(string, string) bool {
  349. return false
  350. }
  351. mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride}
  352. mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride}
  353. cmdBuilder := &exe.CmdBuilder{
  354. MakepkgBin: makepkgBin,
  355. SudoBin: "su",
  356. PacmanBin: pacmanBin,
  357. PacmanConfigPath: "/etc/pacman.conf",
  358. Runner: mockRunner,
  359. SudoLoopEnabled: false,
  360. }
  361. cmdBuilder.Runner = mockRunner
  362. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false)
  363. cmdArgs := parser.MakeArguments()
  364. cmdArgs.AddTarget("yay")
  365. pkgBuildDirs := map[string]string{
  366. "yay": tmpDir,
  367. "jellyfin": tmpDirJfin,
  368. }
  369. errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs)
  370. require.NoError(td, errI)
  371. require.Len(td, mockRunner.ShowCalls, len(tc.wantShow))
  372. require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture))
  373. for i, call := range mockRunner.ShowCalls {
  374. show := call.Args[0].(*exec.Cmd).String()
  375. show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path
  376. show = strings.ReplaceAll(show, tmpDirJfin, "/testdir") // replace the temp dir with a static path
  377. show = strings.ReplaceAll(show, makepkgBin, "makepkg")
  378. show = strings.ReplaceAll(show, pacmanBin, "pacman")
  379. // options are in a different order on different systems and on CI root user is used
  380. assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show)
  381. }
  382. for i, call := range mockRunner.CaptureCalls {
  383. capture := call.Args[0].(*exec.Cmd).String()
  384. capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path
  385. capture = strings.ReplaceAll(capture, tmpDirJfin, "/testdir")
  386. capture = strings.ReplaceAll(capture, makepkgBin, "makepkg")
  387. capture = strings.ReplaceAll(capture, pacmanBin, "pacman")
  388. assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture)
  389. }
  390. })
  391. }
  392. }
  393. func TestInstaller_RunPostHooks(t *testing.T) {
  394. mockDB := &mock.DBExecutor{}
  395. mockRunner := &exe.MockRunner{}
  396. cmdBuilder := &exe.CmdBuilder{
  397. MakepkgBin: "makepkg",
  398. SudoBin: "su",
  399. PacmanBin: "pacman",
  400. PacmanConfigPath: "/etc/pacman.conf",
  401. Runner: mockRunner,
  402. SudoLoopEnabled: false,
  403. }
  404. cmdBuilder.Runner = mockRunner
  405. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false)
  406. called := false
  407. hook := func(ctx context.Context) error {
  408. called = true
  409. return nil
  410. }
  411. installer.AddPostInstallHook(hook)
  412. installer.RunPostInstallHooks(context.Background())
  413. assert.True(t, called)
  414. }
  415. func TestInstaller_CompileFailed(t *testing.T) {
  416. t.Parallel()
  417. makepkgBin := t.TempDir() + "/makepkg"
  418. pacmanBin := t.TempDir() + "/pacman"
  419. f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755)
  420. require.NoError(t, err)
  421. require.NoError(t, f.Close())
  422. f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755)
  423. require.NoError(t, err)
  424. require.NoError(t, f.Close())
  425. type testCase struct {
  426. desc string
  427. targets []map[string]*dep.InstallInfo
  428. lastLayer bool
  429. }
  430. tmpDir := t.TempDir()
  431. testCases := []testCase{
  432. {
  433. desc: "last layer",
  434. lastLayer: true,
  435. targets: []map[string]*dep.InstallInfo{
  436. {
  437. "yay": {
  438. Source: dep.AUR,
  439. Reason: dep.Explicit,
  440. Version: "91.0.0-1",
  441. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  442. AURBase: ptrString("yay"),
  443. },
  444. },
  445. },
  446. },
  447. {
  448. desc: "not last layer",
  449. lastLayer: false,
  450. targets: []map[string]*dep.InstallInfo{
  451. {"bob": {}},
  452. {
  453. "yay": {
  454. Source: dep.AUR,
  455. Reason: dep.Explicit,
  456. Version: "91.0.0-1",
  457. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  458. AURBase: ptrString("yay"),
  459. },
  460. },
  461. },
  462. },
  463. }
  464. for _, tc := range testCases {
  465. tc := tc
  466. t.Run(tc.desc, func(td *testing.T) {
  467. pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst"
  468. captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
  469. return pkgTar, "", nil
  470. }
  471. showOverride := func(cmd *exec.Cmd) error {
  472. if strings.Contains(cmd.String(), "makepkg -cf --noconfirm") && cmd.Dir == tmpDir {
  473. return errors.New("makepkg failed")
  474. }
  475. return nil
  476. }
  477. isCorrectInstalledOverride := func(string, string) bool {
  478. return false
  479. }
  480. mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride}
  481. mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride}
  482. cmdBuilder := &exe.CmdBuilder{
  483. MakepkgBin: makepkgBin,
  484. SudoBin: "su",
  485. PacmanBin: pacmanBin,
  486. Runner: mockRunner,
  487. SudoLoopEnabled: false,
  488. }
  489. cmdBuilder.Runner = mockRunner
  490. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false)
  491. cmdArgs := parser.MakeArguments()
  492. cmdArgs.AddArg("needed")
  493. cmdArgs.AddTarget("yay")
  494. pkgBuildDirs := map[string]string{
  495. "yay": tmpDir,
  496. }
  497. errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs)
  498. if tc.lastLayer {
  499. require.NoError(td, errI) // last layer error
  500. } else {
  501. require.Error(td, errI)
  502. }
  503. err := installer.CompileFailedAndIgnored()
  504. if tc.lastLayer {
  505. require.Error(td, err)
  506. assert.ErrorContains(td, err, "yay")
  507. } else {
  508. require.NoError(td, err)
  509. }
  510. })
  511. }
  512. }
  513. func TestInstaller_InstallSplitPackage(t *testing.T) {
  514. t.Parallel()
  515. makepkgBin := t.TempDir() + "/makepkg"
  516. pacmanBin := t.TempDir() + "/pacman"
  517. f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755)
  518. require.NoError(t, err)
  519. require.NoError(t, f.Close())
  520. f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755)
  521. require.NoError(t, err)
  522. require.NoError(t, f.Close())
  523. type testCase struct {
  524. desc string
  525. wantShow []string
  526. wantCapture []string
  527. targets []map[string]*dep.InstallInfo
  528. }
  529. tmpDir := t.TempDir()
  530. testCases := []testCase{
  531. {
  532. desc: "jellyfin",
  533. targets: []map[string]*dep.InstallInfo{
  534. {"jellyfin": {
  535. Source: dep.AUR,
  536. Reason: dep.Explicit,
  537. Version: "10.8.4-1",
  538. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  539. AURBase: ptrString("jellyfin"),
  540. }},
  541. {
  542. "jellyfin-server": {
  543. Source: dep.AUR,
  544. Reason: dep.Dep,
  545. Version: "10.8.4-1",
  546. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  547. AURBase: ptrString("jellyfin"),
  548. },
  549. "jellyfin-web": {
  550. Source: dep.AUR,
  551. Reason: dep.Dep,
  552. Version: "10.8.4-1",
  553. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  554. AURBase: ptrString("jellyfin"),
  555. },
  556. },
  557. {
  558. "dotnet-runtime-6.0": {
  559. Source: dep.Sync,
  560. Reason: dep.Dep,
  561. Version: "6.0.12.sdk112-1",
  562. SyncDBName: ptrString("community"),
  563. },
  564. "aspnet-runtime": {
  565. Source: dep.Sync,
  566. Reason: dep.Dep,
  567. Version: "6.0.12.sdk112-1",
  568. SyncDBName: ptrString("community"),
  569. },
  570. "dotnet-sdk-6.0": {
  571. Source: dep.Sync,
  572. Reason: dep.MakeDep,
  573. Version: "6.0.12.sdk112-1",
  574. SyncDBName: ptrString("community"),
  575. },
  576. },
  577. },
  578. wantShow: []string{
  579. "pacman -S --config /etc/pacman.conf -- community/dotnet-runtime-6.0 community/aspnet-runtime community/dotnet-sdk-6.0",
  580. "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 aspnet-runtime dotnet-sdk-6.0",
  581. "makepkg --nobuild -fC --ignorearch",
  582. "makepkg -cf --noconfirm --noextract --noprepare --holdver --ignorearch",
  583. "makepkg --nobuild -fC --ignorearch",
  584. "makepkg -c --nobuild --noextract --ignorearch",
  585. "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst /testdir/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst",
  586. "pacman -D -q --asdeps --config /etc/pacman.conf -- jellyfin-server jellyfin-web",
  587. "makepkg --nobuild -fC --ignorearch",
  588. "makepkg -c --nobuild --noextract --ignorearch",
  589. "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-10.8.4-1-x86_64.pkg.tar.zst",
  590. "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin",
  591. },
  592. wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist", "makepkg --packagelist"},
  593. },
  594. }
  595. for _, tc := range testCases {
  596. tc := tc
  597. t.Run(tc.desc, func(td *testing.T) {
  598. pkgTars := []string{
  599. tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst",
  600. tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst",
  601. tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst",
  602. }
  603. captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
  604. return strings.Join(pkgTars, "\n"), "", nil
  605. }
  606. i := 0
  607. showOverride := func(cmd *exec.Cmd) error {
  608. i++
  609. if i == 4 {
  610. for _, pkgTar := range pkgTars {
  611. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  612. require.NoError(td, err)
  613. require.NoError(td, f.Close())
  614. }
  615. }
  616. return nil
  617. }
  618. isCorrectInstalledOverride := func(string, string) bool {
  619. return false
  620. }
  621. mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride}
  622. mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride}
  623. cmdBuilder := &exe.CmdBuilder{
  624. MakepkgBin: makepkgBin,
  625. SudoBin: "su",
  626. PacmanBin: pacmanBin,
  627. PacmanConfigPath: "/etc/pacman.conf",
  628. Runner: mockRunner,
  629. SudoLoopEnabled: false,
  630. }
  631. cmdBuilder.Runner = mockRunner
  632. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false)
  633. cmdArgs := parser.MakeArguments()
  634. cmdArgs.AddTarget("jellyfin")
  635. pkgBuildDirs := map[string]string{
  636. "jellyfin": tmpDir,
  637. }
  638. errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs)
  639. require.NoError(td, errI)
  640. require.Len(td, mockRunner.ShowCalls, len(tc.wantShow))
  641. require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture))
  642. for i, call := range mockRunner.ShowCalls {
  643. show := call.Args[0].(*exec.Cmd).String()
  644. show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path
  645. show = strings.ReplaceAll(show, makepkgBin, "makepkg")
  646. show = strings.ReplaceAll(show, pacmanBin, "pacman")
  647. // options are in a different order on different systems and on CI root user is used
  648. assert.Subset(td, strings.Split(show, " "),
  649. strings.Split(tc.wantShow[i], " "),
  650. fmt.Sprintf("got at %d: %s \n", i, show))
  651. }
  652. for i, call := range mockRunner.CaptureCalls {
  653. capture := call.Args[0].(*exec.Cmd).String()
  654. capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path
  655. capture = strings.ReplaceAll(capture, makepkgBin, "makepkg")
  656. capture = strings.ReplaceAll(capture, pacmanBin, "pacman")
  657. assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture)
  658. }
  659. })
  660. }
  661. }
  662. func TestInstaller_InstallDownloadOnly(t *testing.T) {
  663. t.Parallel()
  664. makepkgBin := t.TempDir() + "/makepkg"
  665. pacmanBin := t.TempDir() + "/pacman"
  666. f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755)
  667. require.NoError(t, err)
  668. require.NoError(t, f.Close())
  669. f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755)
  670. require.NoError(t, err)
  671. require.NoError(t, f.Close())
  672. type testCase struct {
  673. desc string
  674. isInstalled bool
  675. isBuilt bool
  676. wantShow []string
  677. wantCapture []string
  678. }
  679. testCases := []testCase{
  680. {
  681. desc: "not installed and not built",
  682. isInstalled: false,
  683. isBuilt: false,
  684. wantShow: []string{
  685. "makepkg --nobuild -fC --ignorearch",
  686. "makepkg -c --nobuild --noextract --ignorearch",
  687. },
  688. wantCapture: []string{"makepkg --packagelist"},
  689. },
  690. {
  691. desc: "not installed and built",
  692. isInstalled: false,
  693. isBuilt: true,
  694. wantShow: []string{
  695. "makepkg --nobuild -fC --ignorearch",
  696. "makepkg -c --nobuild --noextract --ignorearch",
  697. },
  698. wantCapture: []string{"makepkg --packagelist"},
  699. },
  700. {
  701. desc: "installed",
  702. isInstalled: true,
  703. isBuilt: false,
  704. wantShow: []string{
  705. "makepkg --nobuild -fC --ignorearch",
  706. "makepkg -c --nobuild --noextract --ignorearch",
  707. },
  708. wantCapture: []string{"makepkg --packagelist"},
  709. },
  710. }
  711. for _, tc := range testCases {
  712. tc := tc
  713. t.Run(tc.desc, func(td *testing.T) {
  714. tmpDir := td.TempDir()
  715. pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst"
  716. captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) {
  717. return pkgTar, "", nil
  718. }
  719. i := 0
  720. showOverride := func(cmd *exec.Cmd) error {
  721. i++
  722. if i == 2 {
  723. if !tc.isBuilt {
  724. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  725. require.NoError(td, err)
  726. require.NoError(td, f.Close())
  727. }
  728. }
  729. return nil
  730. }
  731. // create a mock file
  732. if tc.isBuilt {
  733. f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666)
  734. require.NoError(td, err)
  735. require.NoError(td, f.Close())
  736. }
  737. isCorrectInstalledOverride := func(string, string) bool {
  738. return tc.isInstalled
  739. }
  740. mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride}
  741. mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride}
  742. cmdBuilder := &exe.CmdBuilder{
  743. MakepkgBin: makepkgBin,
  744. SudoBin: "su",
  745. PacmanBin: pacmanBin,
  746. Runner: mockRunner,
  747. SudoLoopEnabled: false,
  748. }
  749. cmdBuilder.Runner = mockRunner
  750. installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, true)
  751. cmdArgs := parser.MakeArguments()
  752. cmdArgs.AddTarget("yay")
  753. pkgBuildDirs := map[string]string{
  754. "yay": tmpDir,
  755. }
  756. targets := []map[string]*dep.InstallInfo{
  757. {
  758. "yay": {
  759. Source: dep.AUR,
  760. Reason: dep.Explicit,
  761. Version: "91.0.0-1",
  762. SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"),
  763. AURBase: ptrString("yay"),
  764. },
  765. },
  766. }
  767. errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs)
  768. require.NoError(td, errI)
  769. require.Len(td, mockRunner.ShowCalls, len(tc.wantShow))
  770. require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture))
  771. require.Empty(td, installer.failedAndIgnored)
  772. for i, call := range mockRunner.ShowCalls {
  773. show := call.Args[0].(*exec.Cmd).String()
  774. show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path
  775. show = strings.ReplaceAll(show, makepkgBin, "makepkg")
  776. show = strings.ReplaceAll(show, pacmanBin, "pacman")
  777. // options are in a different order on different systems and on CI root user is used
  778. assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show)
  779. }
  780. for i, call := range mockRunner.CaptureCalls {
  781. capture := call.Args[0].(*exec.Cmd).String()
  782. capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path
  783. capture = strings.ReplaceAll(capture, makepkgBin, "makepkg")
  784. capture = strings.ReplaceAll(capture, pacmanBin, "pacman")
  785. assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture)
  786. }
  787. })
  788. }
  789. }