cache_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. package blob
  2. import (
  3. "crypto/sha256"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "os"
  9. "path/filepath"
  10. "slices"
  11. "strings"
  12. "testing"
  13. "time"
  14. "github.com/ollama/ollama/server/internal/internal/testutil"
  15. )
  16. func init() {
  17. debug = true
  18. }
  19. var epoch = func() time.Time {
  20. d := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
  21. if d.IsZero() {
  22. panic("time zero")
  23. }
  24. return d
  25. }()
  26. func TestOpenErrors(t *testing.T) {
  27. exe, err := os.Executable()
  28. if err != nil {
  29. panic(err)
  30. }
  31. cases := []struct {
  32. dir string
  33. err string
  34. }{
  35. {t.TempDir(), ""},
  36. {"", "empty directory name"},
  37. {exe, "not a directory"},
  38. }
  39. for _, tt := range cases {
  40. t.Run(tt.dir, func(t *testing.T) {
  41. _, err := Open(tt.dir)
  42. if tt.err == "" {
  43. if err != nil {
  44. t.Fatal(err)
  45. }
  46. return
  47. }
  48. if err == nil {
  49. t.Fatal("expected error")
  50. }
  51. if !strings.Contains(err.Error(), tt.err) {
  52. t.Fatalf("err = %v, want %q", err, tt.err)
  53. }
  54. })
  55. }
  56. }
  57. func TestGetFile(t *testing.T) {
  58. t.Chdir(t.TempDir())
  59. c, err := Open(".")
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. d := mkdigest("1")
  64. got := c.GetFile(d)
  65. cleaned := filepath.Clean(got)
  66. if cleaned != got {
  67. t.Fatalf("got is unclean: %q", got)
  68. }
  69. if !filepath.IsAbs(got) {
  70. t.Fatal("got is not absolute")
  71. }
  72. abs, _ := filepath.Abs(c.dir)
  73. if !strings.HasPrefix(got, abs) {
  74. t.Fatalf("got is not local to %q", c.dir)
  75. }
  76. }
  77. func TestBasic(t *testing.T) {
  78. c, err := Open(t.TempDir())
  79. if err != nil {
  80. t.Fatal(err)
  81. }
  82. now := epoch
  83. c.now = func() time.Time { return now }
  84. checkEntry := entryChecker(t, c)
  85. checkFailed := func(err error) {
  86. if err == nil {
  87. t.Helper()
  88. t.Fatal("expected error")
  89. }
  90. }
  91. _, err = c.Resolve("invalid")
  92. checkFailed(err)
  93. _, err = c.Resolve("h/n/m:t")
  94. checkFailed(err)
  95. dx := mkdigest("x")
  96. d, err := c.Resolve(fmt.Sprintf("h/n/m:t@%s", dx))
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. if d != dx {
  101. t.Fatalf("d = %v, want %v", d, dx)
  102. }
  103. _, err = c.Get(Digest{})
  104. checkFailed(err)
  105. // not committed yet
  106. _, err = c.Get(dx)
  107. checkFailed(err)
  108. err = PutBytes(c, dx, "!")
  109. checkFailed(err)
  110. err = PutBytes(c, dx, "x")
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. checkEntry(dx, 1, now)
  115. t0 := now
  116. now = now.Add(1*time.Hour + 1*time.Minute)
  117. err = PutBytes(c, dx, "x")
  118. if err != nil {
  119. t.Fatal(err)
  120. }
  121. // check not updated
  122. checkEntry(dx, 1, t0)
  123. }
  124. type sleepFunc func(d time.Duration) time.Time
  125. func openTester(t *testing.T) (*DiskCache, sleepFunc) {
  126. t.Helper()
  127. c, err := Open(t.TempDir())
  128. if err != nil {
  129. t.Fatal(err)
  130. }
  131. now := epoch
  132. c.now = func() time.Time { return now }
  133. return c, func(d time.Duration) time.Time {
  134. now = now.Add(d)
  135. return now
  136. }
  137. }
  138. func TestManifestPath(t *testing.T) {
  139. check := testutil.Checker(t)
  140. c, sleep := openTester(t)
  141. d1 := mkdigest("1")
  142. err := PutBytes(c, d1, "1")
  143. check(err)
  144. err = c.Link("h/n/m:t", d1)
  145. check(err)
  146. t0 := sleep(0)
  147. sleep(1 * time.Hour)
  148. err = c.Link("h/n/m:t", d1) // nop expected
  149. check(err)
  150. file := must(c.manifestPath("h/n/m:t"))
  151. info, err := os.Stat(file)
  152. check(err)
  153. testutil.CheckTime(t, info.ModTime(), t0)
  154. }
  155. func TestManifestExistsWithoutBlob(t *testing.T) {
  156. t.Chdir(t.TempDir())
  157. check := testutil.Checker(t)
  158. c, err := Open(".")
  159. check(err)
  160. checkEntry := entryChecker(t, c)
  161. man := must(c.manifestPath("h/n/m:t"))
  162. os.MkdirAll(filepath.Dir(man), 0o777)
  163. testutil.WriteFile(t, man, "1")
  164. got, err := c.Resolve("h/n/m:t")
  165. check(err)
  166. want := mkdigest("1")
  167. if got != want {
  168. t.Fatalf("got = %v, want %v", got, want)
  169. }
  170. e, err := c.Get(got)
  171. check(err)
  172. checkEntry(got, 1, e.Time)
  173. }
  174. func TestPut(t *testing.T) {
  175. c, sleep := openTester(t)
  176. check := testutil.Checker(t)
  177. checkEntry := entryChecker(t, c)
  178. d := mkdigest("hello, world")
  179. err := PutBytes(c, d, "hello")
  180. if err == nil {
  181. t.Fatal("expected error")
  182. }
  183. got, err := c.Get(d)
  184. if !errors.Is(err, fs.ErrNotExist) {
  185. t.Fatalf("expected error, got %v", got)
  186. }
  187. // Put a valid blob
  188. err = PutBytes(c, d, "hello, world")
  189. check(err)
  190. checkEntry(d, 12, sleep(0))
  191. // Put a blob with content that does not hash to the digest
  192. err = PutBytes(c, d, "hello")
  193. if err == nil {
  194. t.Fatal("expected error")
  195. }
  196. checkNotExists(t, c, d)
  197. // Put the valid blob back and check it
  198. err = PutBytes(c, d, "hello, world")
  199. check(err)
  200. checkEntry(d, 12, sleep(0))
  201. // Put a blob that errors during Read
  202. err = c.Put(d, &errOnBangReader{s: "!"}, 1)
  203. if err == nil {
  204. t.Fatal("expected error")
  205. }
  206. checkNotExists(t, c, d)
  207. // Put valid blob back and check it
  208. err = PutBytes(c, d, "hello, world")
  209. check(err)
  210. checkEntry(d, 12, sleep(0))
  211. // Put a blob with mismatched size
  212. err = c.Put(d, strings.NewReader("hello, world"), 11)
  213. if err == nil {
  214. t.Fatal("expected error")
  215. }
  216. checkNotExists(t, c, d)
  217. // Final byte does not match the digest (testing commit phase)
  218. err = PutBytes(c, d, "hello, world$")
  219. if err == nil {
  220. t.Fatal("expected error")
  221. }
  222. checkNotExists(t, c, d)
  223. reset := c.setTestHookBeforeFinalWrite(func(f *os.File) {
  224. // change mode to read-only
  225. f.Truncate(0)
  226. f.Chmod(0o400)
  227. f.Close()
  228. f1, err := os.OpenFile(f.Name(), os.O_RDONLY, 0)
  229. if err != nil {
  230. t.Fatal(err)
  231. }
  232. t.Cleanup(func() { f1.Close() })
  233. *f = *f1
  234. })
  235. defer reset()
  236. err = PutBytes(c, d, "hello, world")
  237. if err == nil {
  238. t.Fatal("expected error")
  239. }
  240. checkNotExists(t, c, d)
  241. reset()
  242. }
  243. func TestImport(t *testing.T) {
  244. c, _ := openTester(t)
  245. checkEntry := entryChecker(t, c)
  246. want := mkdigest("x")
  247. got, err := c.Import(strings.NewReader("x"), 1)
  248. if err != nil {
  249. t.Fatal(err)
  250. }
  251. if want != got {
  252. t.Fatalf("digest = %v, want %v", got, want)
  253. }
  254. checkEntry(want, 1, epoch)
  255. got, err = c.Import(strings.NewReader("x"), 1)
  256. if err != nil {
  257. t.Fatal(err)
  258. }
  259. if want != got {
  260. t.Fatalf("digest = %v, want %v", got, want)
  261. }
  262. checkEntry(want, 1, epoch)
  263. }
  264. func (c *DiskCache) setTestHookBeforeFinalWrite(h func(*os.File)) (reset func()) {
  265. old := c.testHookBeforeFinalWrite
  266. c.testHookBeforeFinalWrite = h
  267. return func() { c.testHookBeforeFinalWrite = old }
  268. }
  269. func TestPutGetZero(t *testing.T) {
  270. c, sleep := openTester(t)
  271. check := testutil.Checker(t)
  272. checkEntry := entryChecker(t, c)
  273. d := mkdigest("x")
  274. err := PutBytes(c, d, "x")
  275. check(err)
  276. checkEntry(d, 1, sleep(0))
  277. err = os.Truncate(c.GetFile(d), 0)
  278. check(err)
  279. _, err = c.Get(d)
  280. if !errors.Is(err, fs.ErrNotExist) {
  281. t.Fatalf("err = %v, want fs.ErrNotExist", err)
  282. }
  283. }
  284. func TestPutZero(t *testing.T) {
  285. c, _ := openTester(t)
  286. d := mkdigest("x")
  287. err := c.Put(d, strings.NewReader("x"), 0) // size == 0 (not size of content)
  288. testutil.Check(t, err)
  289. checkNotExists(t, c, d)
  290. }
  291. func TestCommit(t *testing.T) {
  292. check := testutil.Checker(t)
  293. c, err := Open(t.TempDir())
  294. if err != nil {
  295. t.Fatal(err)
  296. }
  297. checkEntry := entryChecker(t, c)
  298. now := epoch
  299. c.now = func() time.Time { return now }
  300. d1 := mkdigest("1")
  301. err = c.Link("h/n/m:t", d1)
  302. if !errors.Is(err, fs.ErrNotExist) {
  303. t.Fatalf("err = %v, want fs.ErrNotExist", err)
  304. }
  305. err = PutBytes(c, d1, "1")
  306. check(err)
  307. err = c.Link("h/n/m:t", d1)
  308. check(err)
  309. got, err := c.Resolve("h/n/m:t")
  310. check(err)
  311. if got != d1 {
  312. t.Fatalf("d = %v, want %v", got, d1)
  313. }
  314. // commit again, more than 1 byte
  315. d2 := mkdigest("22")
  316. err = PutBytes(c, d2, "22")
  317. check(err)
  318. err = c.Link("h/n/m:t", d2)
  319. check(err)
  320. checkEntry(d2, 2, now)
  321. filename := must(c.manifestPath("h/n/m:t"))
  322. data, err := os.ReadFile(filename)
  323. check(err)
  324. if string(data) != "22" {
  325. t.Fatalf("data = %q, want %q", data, "22")
  326. }
  327. t0 := now
  328. now = now.Add(1 * time.Hour)
  329. err = c.Link("h/n/m:t", d2) // same contents; nop
  330. check(err)
  331. info, err := os.Stat(filename)
  332. check(err)
  333. testutil.CheckTime(t, info.ModTime(), t0)
  334. }
  335. func TestManifestInvalidBlob(t *testing.T) {
  336. c, _ := openTester(t)
  337. d := mkdigest("1")
  338. err := c.Link("h/n/m:t", d)
  339. if err == nil {
  340. t.Fatal("expected error")
  341. }
  342. checkNotExists(t, c, d)
  343. err = PutBytes(c, d, "1")
  344. testutil.Check(t, err)
  345. err = os.WriteFile(c.GetFile(d), []byte("invalid"), 0o666)
  346. if err != nil {
  347. t.Fatal(err)
  348. }
  349. err = c.Link("h/n/m:t", d)
  350. if !strings.Contains(err.Error(), "underfoot") {
  351. t.Fatalf("err = %v, want error to contain %q", err, "underfoot")
  352. }
  353. }
  354. func TestManifestNameReuse(t *testing.T) {
  355. t.Run("case-insensitive", func(t *testing.T) {
  356. // This should run on all file system types.
  357. testManifestNameReuse(t)
  358. })
  359. t.Run("case-sensitive", func(t *testing.T) {
  360. useCaseInsensitiveTempDir(t)
  361. testManifestNameReuse(t)
  362. })
  363. }
  364. func testManifestNameReuse(t *testing.T) {
  365. check := testutil.Checker(t)
  366. c, _ := openTester(t)
  367. d1 := mkdigest("1")
  368. err := PutBytes(c, d1, "1")
  369. check(err)
  370. err = c.Link("h/n/m:t", d1)
  371. check(err)
  372. d2 := mkdigest("22")
  373. err = PutBytes(c, d2, "22")
  374. check(err)
  375. err = c.Link("H/N/M:T", d2)
  376. check(err)
  377. var g [2]Digest
  378. g[0], err = c.Resolve("h/n/m:t")
  379. check(err)
  380. g[1], err = c.Resolve("H/N/M:T")
  381. check(err)
  382. w := [2]Digest{d2, d2}
  383. if g != w {
  384. t.Fatalf("g = %v, want %v", g, w)
  385. }
  386. var got []string
  387. for l, err := range c.links() {
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. got = append(got, l)
  392. }
  393. want := []string{"manifests/h/n/m/t"}
  394. if !slices.Equal(got, want) {
  395. t.Fatalf("got = %v, want %v", got, want)
  396. }
  397. // relink with different case
  398. err = c.Unlink("h/n/m:t")
  399. check(err)
  400. err = c.Link("h/n/m:T", d1)
  401. check(err)
  402. got = got[:0]
  403. for l, err := range c.links() {
  404. if err != nil {
  405. t.Fatal(err)
  406. }
  407. got = append(got, l)
  408. }
  409. // we should have only one link that is same case as the last link
  410. want = []string{"manifests/h/n/m/T"}
  411. if !slices.Equal(got, want) {
  412. t.Fatalf("got = %v, want %v", got, want)
  413. }
  414. }
  415. func TestManifestFile(t *testing.T) {
  416. cases := []struct {
  417. in string
  418. want string
  419. }{
  420. {"", ""},
  421. // valid names
  422. {"h/n/m:t", "/manifests/h/n/m/t"},
  423. {"hh/nn/mm:tt", "/manifests/hh/nn/mm/tt"},
  424. {"%/%/%/%", ""},
  425. // already a path
  426. {"h/n/m/t", ""},
  427. // refs are not names
  428. {"h/n/m:t@sha256-1", ""},
  429. {"m@sha256-1", ""},
  430. {"n/m:t@sha256-1", ""},
  431. }
  432. c, _ := openTester(t)
  433. for _, tt := range cases {
  434. t.Run(tt.in, func(t *testing.T) {
  435. got, err := c.manifestPath(tt.in)
  436. if err != nil && tt.want != "" {
  437. t.Fatalf("unexpected error: %v", err)
  438. }
  439. if err == nil && tt.want == "" {
  440. t.Fatalf("expected error")
  441. }
  442. dir := filepath.ToSlash(c.dir)
  443. got = filepath.ToSlash(got)
  444. got = strings.TrimPrefix(got, dir)
  445. if got != tt.want {
  446. t.Fatalf("got = %q, want %q", got, tt.want)
  447. }
  448. })
  449. }
  450. }
  451. func TestNames(t *testing.T) {
  452. c, _ := openTester(t)
  453. check := testutil.Checker(t)
  454. check(PutBytes(c, mkdigest("1"), "1"))
  455. check(PutBytes(c, mkdigest("2"), "2"))
  456. check(c.Link("h/n/m:t", mkdigest("1")))
  457. check(c.Link("h/n/m:u", mkdigest("2")))
  458. var got []string
  459. for l, err := range c.Links() {
  460. if err != nil {
  461. t.Fatal(err)
  462. }
  463. got = append(got, l)
  464. }
  465. want := []string{"h/n/m:t", "h/n/m:u"}
  466. if !slices.Equal(got, want) {
  467. t.Fatalf("got = %v, want %v", got, want)
  468. }
  469. }
  470. func mkdigest(s string) Digest {
  471. return Digest{sha256.Sum256([]byte(s))}
  472. }
  473. func checkNotExists(t *testing.T, c *DiskCache, d Digest) {
  474. t.Helper()
  475. _, err := c.Get(d)
  476. if !errors.Is(err, fs.ErrNotExist) {
  477. t.Fatalf("err = %v, want fs.ErrNotExist", err)
  478. }
  479. }
  480. func entryChecker(t *testing.T, c *DiskCache) func(Digest, int64, time.Time) {
  481. t.Helper()
  482. return func(d Digest, size int64, mod time.Time) {
  483. t.Helper()
  484. t.Run("checkEntry:"+d.String(), func(t *testing.T) {
  485. t.Helper()
  486. defer func() {
  487. if t.Failed() {
  488. dumpCacheContents(t, c)
  489. }
  490. }()
  491. e, err := c.Get(d)
  492. if size == 0 && errors.Is(err, fs.ErrNotExist) {
  493. err = nil
  494. }
  495. if err != nil {
  496. t.Fatal(err)
  497. }
  498. if e.Digest != d {
  499. t.Errorf("e.Digest = %v, want %v", e.Digest, d)
  500. }
  501. if e.Size != size {
  502. t.Fatalf("e.Size = %v, want %v", e.Size, size)
  503. }
  504. testutil.CheckTime(t, e.Time, mod)
  505. info, err := os.Stat(c.GetFile(d))
  506. if err != nil {
  507. t.Fatal(err)
  508. }
  509. if info.Size() != size {
  510. t.Fatalf("info.Size = %v, want %v", info.Size(), size)
  511. }
  512. testutil.CheckTime(t, info.ModTime(), mod)
  513. })
  514. }
  515. }
  516. func must[T any](v T, err error) T {
  517. if err != nil {
  518. panic(err)
  519. }
  520. return v
  521. }
  522. func TestNameToPath(t *testing.T) {
  523. _, err := nameToPath("h/n/m:t")
  524. if err != nil {
  525. t.Fatal(err)
  526. }
  527. }
  528. type errOnBangReader struct {
  529. s string
  530. n int
  531. }
  532. func (e *errOnBangReader) Read(p []byte) (int, error) {
  533. if len(p) < 1 {
  534. return 0, io.ErrShortBuffer
  535. }
  536. if e.n >= len(p) {
  537. return 0, io.EOF
  538. }
  539. if e.s[e.n] == '!' {
  540. return 0, errors.New("bang")
  541. }
  542. p[0] = e.s[e.n]
  543. e.n++
  544. return 1, nil
  545. }
  546. func dumpCacheContents(t *testing.T, c *DiskCache) {
  547. t.Helper()
  548. var b strings.Builder
  549. fsys := os.DirFS(c.dir)
  550. fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
  551. t.Helper()
  552. if err != nil {
  553. return err
  554. }
  555. info, err := d.Info()
  556. if err != nil {
  557. return err
  558. }
  559. // Format like ls:
  560. //
  561. // ; ls -la
  562. // drwxr-xr-x 224 Jan 13 14:22 blob/sha256-123
  563. // drwxr-xr-x 224 Jan 13 14:22 manifest/h/n/m
  564. fmt.Fprintf(&b, " %s % 4d %s %s\n",
  565. info.Mode(),
  566. info.Size(),
  567. info.ModTime().Format("Jan 2 15:04"),
  568. path,
  569. )
  570. return nil
  571. })
  572. t.Log()
  573. t.Logf("cache contents:\n%s", b.String())
  574. }