aur_install_test.go 27 KB

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