aur_install_test.go 27 KB

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