cmd_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. package cmd
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "os"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/google/go-cmp/cmp"
  14. "github.com/spf13/cobra"
  15. "github.com/ollama/ollama/api"
  16. )
  17. func TestShowInfo(t *testing.T) {
  18. t.Run("bare details", func(t *testing.T) {
  19. var b bytes.Buffer
  20. if err := showInfo(&api.ShowResponse{
  21. Details: api.ModelDetails{
  22. Family: "test",
  23. ParameterSize: "7B",
  24. QuantizationLevel: "FP16",
  25. },
  26. }, false, &b); err != nil {
  27. t.Fatal(err)
  28. }
  29. expect := ` Model
  30. architecture test
  31. parameters 7B
  32. quantization FP16
  33. `
  34. if diff := cmp.Diff(expect, b.String()); diff != "" {
  35. t.Errorf("unexpected output (-want +got):\n%s", diff)
  36. }
  37. })
  38. t.Run("bare model info", func(t *testing.T) {
  39. var b bytes.Buffer
  40. if err := showInfo(&api.ShowResponse{
  41. ModelInfo: map[string]any{
  42. "general.architecture": "test",
  43. "general.parameter_count": float64(7_000_000_000),
  44. "test.context_length": float64(0),
  45. "test.embedding_length": float64(0),
  46. },
  47. Details: api.ModelDetails{
  48. Family: "test",
  49. ParameterSize: "7B",
  50. QuantizationLevel: "FP16",
  51. },
  52. }, false, &b); err != nil {
  53. t.Fatal(err)
  54. }
  55. expect := ` Model
  56. architecture test
  57. parameters 7B
  58. context length 0
  59. embedding length 0
  60. quantization FP16
  61. `
  62. if diff := cmp.Diff(expect, b.String()); diff != "" {
  63. t.Errorf("unexpected output (-want +got):\n%s", diff)
  64. }
  65. })
  66. t.Run("verbose model", func(t *testing.T) {
  67. var b bytes.Buffer
  68. if err := showInfo(&api.ShowResponse{
  69. Details: api.ModelDetails{
  70. Family: "test",
  71. ParameterSize: "8B",
  72. QuantizationLevel: "FP16",
  73. },
  74. Parameters: `
  75. stop up`,
  76. ModelInfo: map[string]any{
  77. "general.architecture": "test",
  78. "general.parameter_count": float64(8_000_000_000),
  79. "test.context_length": float64(1000),
  80. "test.embedding_length": float64(11434),
  81. },
  82. Tensors: []api.Tensor{
  83. {Name: "blk.0.attn_k.weight", Type: "BF16", Shape: []uint64{42, 3117}},
  84. {Name: "blk.0.attn_q.weight", Type: "FP16", Shape: []uint64{3117, 42}},
  85. },
  86. }, true, &b); err != nil {
  87. t.Fatal(err)
  88. }
  89. expect := ` Model
  90. architecture test
  91. parameters 8B
  92. context length 1000
  93. embedding length 11434
  94. quantization FP16
  95. Parameters
  96. stop up
  97. Metadata
  98. general.architecture test
  99. general.parameter_count 8e+09
  100. test.context_length 1000
  101. test.embedding_length 11434
  102. Tensors
  103. blk.0.attn_k.weight BF16 [42 3117]
  104. blk.0.attn_q.weight FP16 [3117 42]
  105. `
  106. if diff := cmp.Diff(expect, b.String()); diff != "" {
  107. t.Errorf("unexpected output (-want +got):\n%s", diff)
  108. }
  109. })
  110. t.Run("parameters", func(t *testing.T) {
  111. var b bytes.Buffer
  112. if err := showInfo(&api.ShowResponse{
  113. Details: api.ModelDetails{
  114. Family: "test",
  115. ParameterSize: "7B",
  116. QuantizationLevel: "FP16",
  117. },
  118. Parameters: `
  119. stop never
  120. stop gonna
  121. stop give
  122. stop you
  123. stop up
  124. temperature 99`,
  125. }, false, &b); err != nil {
  126. t.Fatal(err)
  127. }
  128. expect := ` Model
  129. architecture test
  130. parameters 7B
  131. quantization FP16
  132. Parameters
  133. stop never
  134. stop gonna
  135. stop give
  136. stop you
  137. stop up
  138. temperature 99
  139. `
  140. if diff := cmp.Diff(expect, b.String()); diff != "" {
  141. t.Errorf("unexpected output (-want +got):\n%s", diff)
  142. }
  143. })
  144. t.Run("project info", func(t *testing.T) {
  145. var b bytes.Buffer
  146. if err := showInfo(&api.ShowResponse{
  147. Details: api.ModelDetails{
  148. Family: "test",
  149. ParameterSize: "7B",
  150. QuantizationLevel: "FP16",
  151. },
  152. ProjectorInfo: map[string]any{
  153. "general.architecture": "clip",
  154. "general.parameter_count": float64(133_700_000),
  155. "clip.vision.embedding_length": float64(0),
  156. "clip.vision.projection_dim": float64(0),
  157. },
  158. }, false, &b); err != nil {
  159. t.Fatal(err)
  160. }
  161. expect := ` Model
  162. architecture test
  163. parameters 7B
  164. quantization FP16
  165. Projector
  166. architecture clip
  167. parameters 133.70M
  168. embedding length 0
  169. dimensions 0
  170. `
  171. if diff := cmp.Diff(expect, b.String()); diff != "" {
  172. t.Errorf("unexpected output (-want +got):\n%s", diff)
  173. }
  174. })
  175. t.Run("system", func(t *testing.T) {
  176. var b bytes.Buffer
  177. if err := showInfo(&api.ShowResponse{
  178. Details: api.ModelDetails{
  179. Family: "test",
  180. ParameterSize: "7B",
  181. QuantizationLevel: "FP16",
  182. },
  183. System: `You are a pirate!
  184. Ahoy, matey!
  185. Weigh anchor!
  186. `,
  187. }, false, &b); err != nil {
  188. t.Fatal(err)
  189. }
  190. expect := ` Model
  191. architecture test
  192. parameters 7B
  193. quantization FP16
  194. System
  195. You are a pirate!
  196. Ahoy, matey!
  197. `
  198. if diff := cmp.Diff(expect, b.String()); diff != "" {
  199. t.Errorf("unexpected output (-want +got):\n%s", diff)
  200. }
  201. })
  202. t.Run("license", func(t *testing.T) {
  203. var b bytes.Buffer
  204. license := "MIT License\nCopyright (c) Ollama\n"
  205. if err := showInfo(&api.ShowResponse{
  206. Details: api.ModelDetails{
  207. Family: "test",
  208. ParameterSize: "7B",
  209. QuantizationLevel: "FP16",
  210. },
  211. License: license,
  212. }, false, &b); err != nil {
  213. t.Fatal(err)
  214. }
  215. expect := ` Model
  216. architecture test
  217. parameters 7B
  218. quantization FP16
  219. License
  220. MIT License
  221. Copyright (c) Ollama
  222. `
  223. if diff := cmp.Diff(expect, b.String()); diff != "" {
  224. t.Errorf("unexpected output (-want +got):\n%s", diff)
  225. }
  226. })
  227. }
  228. func TestDeleteHandler(t *testing.T) {
  229. stopped := false
  230. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  231. if r.URL.Path == "/api/delete" && r.Method == http.MethodDelete {
  232. var req api.DeleteRequest
  233. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  234. http.Error(w, err.Error(), http.StatusBadRequest)
  235. return
  236. }
  237. if req.Name == "test-model" {
  238. w.WriteHeader(http.StatusOK)
  239. } else {
  240. w.WriteHeader(http.StatusNotFound)
  241. }
  242. return
  243. }
  244. if r.URL.Path == "/api/generate" && r.Method == http.MethodPost {
  245. var req api.GenerateRequest
  246. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  247. http.Error(w, err.Error(), http.StatusBadRequest)
  248. return
  249. }
  250. if req.Model == "test-model" {
  251. w.WriteHeader(http.StatusOK)
  252. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  253. Done: true,
  254. }); err != nil {
  255. http.Error(w, err.Error(), http.StatusInternalServerError)
  256. }
  257. stopped = true
  258. return
  259. } else {
  260. w.WriteHeader(http.StatusNotFound)
  261. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  262. Done: false,
  263. }); err != nil {
  264. http.Error(w, err.Error(), http.StatusInternalServerError)
  265. }
  266. }
  267. }
  268. }))
  269. t.Setenv("OLLAMA_HOST", mockServer.URL)
  270. t.Cleanup(mockServer.Close)
  271. cmd := &cobra.Command{}
  272. cmd.SetContext(context.TODO())
  273. if err := DeleteHandler(cmd, []string{"test-model"}); err != nil {
  274. t.Fatalf("DeleteHandler failed: %v", err)
  275. }
  276. if !stopped {
  277. t.Fatal("Model was not stopped before deletion")
  278. }
  279. err := DeleteHandler(cmd, []string{"test-model-not-found"})
  280. if err == nil || !strings.Contains(err.Error(), "unable to stop existing running model \"test-model-not-found\"") {
  281. t.Fatalf("DeleteHandler failed: expected error about stopping non-existent model, got %v", err)
  282. }
  283. }
  284. func TestGetModelfileName(t *testing.T) {
  285. tests := []struct {
  286. name string
  287. modelfileName string
  288. fileExists bool
  289. expectedName string
  290. expectedErr error
  291. }{
  292. {
  293. name: "no modelfile specified, no modelfile exists",
  294. modelfileName: "",
  295. fileExists: false,
  296. expectedName: "",
  297. expectedErr: os.ErrNotExist,
  298. },
  299. {
  300. name: "no modelfile specified, modelfile exists",
  301. modelfileName: "",
  302. fileExists: true,
  303. expectedName: "Modelfile",
  304. expectedErr: nil,
  305. },
  306. {
  307. name: "modelfile specified, no modelfile exists",
  308. modelfileName: "crazyfile",
  309. fileExists: false,
  310. expectedName: "",
  311. expectedErr: os.ErrNotExist,
  312. },
  313. {
  314. name: "modelfile specified, modelfile exists",
  315. modelfileName: "anotherfile",
  316. fileExists: true,
  317. expectedName: "anotherfile",
  318. expectedErr: nil,
  319. },
  320. }
  321. for _, tt := range tests {
  322. t.Run(tt.name, func(t *testing.T) {
  323. cmd := &cobra.Command{
  324. Use: "fakecmd",
  325. }
  326. cmd.Flags().String("file", "", "path to modelfile")
  327. var expectedFilename string
  328. if tt.fileExists {
  329. tempDir, err := os.MkdirTemp("", "modelfiledir")
  330. defer os.RemoveAll(tempDir)
  331. if err != nil {
  332. t.Fatalf("temp modelfile dir creation failed: %v", err)
  333. }
  334. var fn string
  335. if tt.modelfileName != "" {
  336. fn = tt.modelfileName
  337. } else {
  338. fn = "Modelfile"
  339. }
  340. tempFile, err := os.CreateTemp(tempDir, fn)
  341. if err != nil {
  342. t.Fatalf("temp modelfile creation failed: %v", err)
  343. }
  344. expectedFilename = tempFile.Name()
  345. err = cmd.Flags().Set("file", expectedFilename)
  346. if err != nil {
  347. t.Fatalf("couldn't set file flag: %v", err)
  348. }
  349. } else {
  350. expectedFilename = tt.expectedName
  351. if tt.modelfileName != "" {
  352. err := cmd.Flags().Set("file", tt.modelfileName)
  353. if err != nil {
  354. t.Fatalf("couldn't set file flag: %v", err)
  355. }
  356. }
  357. }
  358. actualFilename, actualErr := getModelfileName(cmd)
  359. if actualFilename != expectedFilename {
  360. t.Errorf("expected filename: '%s' actual filename: '%s'", expectedFilename, actualFilename)
  361. }
  362. if tt.expectedErr != os.ErrNotExist {
  363. if actualErr != tt.expectedErr {
  364. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  365. }
  366. } else {
  367. if !os.IsNotExist(actualErr) {
  368. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  369. }
  370. }
  371. })
  372. }
  373. }
  374. func TestPushHandler(t *testing.T) {
  375. tests := []struct {
  376. name string
  377. modelName string
  378. serverResponse map[string]func(w http.ResponseWriter, r *http.Request)
  379. expectedError string
  380. expectedOutput string
  381. }{
  382. {
  383. name: "successful push",
  384. modelName: "test-model",
  385. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  386. "/api/push": func(w http.ResponseWriter, r *http.Request) {
  387. if r.Method != http.MethodPost {
  388. t.Errorf("expected POST request, got %s", r.Method)
  389. }
  390. var req api.PushRequest
  391. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  392. http.Error(w, err.Error(), http.StatusBadRequest)
  393. return
  394. }
  395. if req.Name != "test-model" {
  396. t.Errorf("expected model name 'test-model', got %s", req.Name)
  397. }
  398. // Simulate progress updates
  399. responses := []api.ProgressResponse{
  400. {Status: "preparing manifest"},
  401. {Digest: "sha256:abc123456789", Total: 100, Completed: 50},
  402. {Digest: "sha256:abc123456789", Total: 100, Completed: 100},
  403. }
  404. for _, resp := range responses {
  405. if err := json.NewEncoder(w).Encode(resp); err != nil {
  406. http.Error(w, err.Error(), http.StatusInternalServerError)
  407. return
  408. }
  409. w.(http.Flusher).Flush()
  410. }
  411. },
  412. },
  413. expectedOutput: "\nYou can find your model at:\n\n\thttps://ollama.com/test-model\n",
  414. },
  415. {
  416. name: "unauthorized push",
  417. modelName: "unauthorized-model",
  418. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  419. "/api/push": func(w http.ResponseWriter, r *http.Request) {
  420. w.Header().Set("Content-Type", "application/json")
  421. w.WriteHeader(http.StatusUnauthorized)
  422. err := json.NewEncoder(w).Encode(map[string]string{
  423. "error": "access denied",
  424. })
  425. if err != nil {
  426. t.Fatal(err)
  427. }
  428. },
  429. },
  430. expectedError: "you are not authorized to push to this namespace, create the model under a namespace you own",
  431. },
  432. }
  433. for _, tt := range tests {
  434. t.Run(tt.name, func(t *testing.T) {
  435. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  436. if handler, ok := tt.serverResponse[r.URL.Path]; ok {
  437. handler(w, r)
  438. return
  439. }
  440. http.Error(w, "not found", http.StatusNotFound)
  441. }))
  442. defer mockServer.Close()
  443. t.Setenv("OLLAMA_HOST", mockServer.URL)
  444. cmd := &cobra.Command{}
  445. cmd.Flags().Bool("insecure", false, "")
  446. cmd.SetContext(context.TODO())
  447. // Redirect stderr to capture progress output
  448. oldStderr := os.Stderr
  449. r, w, _ := os.Pipe()
  450. os.Stderr = w
  451. // Capture stdout for the "Model pushed" message
  452. oldStdout := os.Stdout
  453. outR, outW, _ := os.Pipe()
  454. os.Stdout = outW
  455. err := PushHandler(cmd, []string{tt.modelName})
  456. // Restore stderr
  457. w.Close()
  458. os.Stderr = oldStderr
  459. // drain the pipe
  460. if _, err := io.ReadAll(r); err != nil {
  461. t.Fatal(err)
  462. }
  463. // Restore stdout and get output
  464. outW.Close()
  465. os.Stdout = oldStdout
  466. stdout, _ := io.ReadAll(outR)
  467. if tt.expectedError == "" {
  468. if err != nil {
  469. t.Errorf("expected no error, got %v", err)
  470. }
  471. if tt.expectedOutput != "" {
  472. if got := string(stdout); got != tt.expectedOutput {
  473. t.Errorf("expected output %q, got %q", tt.expectedOutput, got)
  474. }
  475. }
  476. } else {
  477. if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
  478. t.Errorf("expected error containing %q, got %v", tt.expectedError, err)
  479. }
  480. }
  481. })
  482. }
  483. }
  484. func TestListHandler(t *testing.T) {
  485. tests := []struct {
  486. name string
  487. args []string
  488. serverResponse []api.ListModelResponse
  489. expectedError string
  490. expectedOutput string
  491. }{
  492. {
  493. name: "list all models",
  494. args: []string{},
  495. serverResponse: []api.ListModelResponse{
  496. {Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  497. {Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
  498. },
  499. expectedOutput: "NAME ID SIZE MODIFIED \n" +
  500. "model1 sha256:abc12 1.0 KB 24 hours ago \n" +
  501. "model2 sha256:def45 2.0 KB 2 days ago \n",
  502. },
  503. {
  504. name: "filter models by prefix",
  505. args: []string{"model1"},
  506. serverResponse: []api.ListModelResponse{
  507. {Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  508. {Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  509. },
  510. expectedOutput: "NAME ID SIZE MODIFIED \n" +
  511. "model1 sha256:abc12 1.0 KB 24 hours ago \n",
  512. },
  513. {
  514. name: "server error",
  515. args: []string{},
  516. expectedError: "server error",
  517. },
  518. }
  519. for _, tt := range tests {
  520. t.Run(tt.name, func(t *testing.T) {
  521. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  522. if r.URL.Path != "/api/tags" || r.Method != http.MethodGet {
  523. t.Errorf("unexpected request to %s %s", r.Method, r.URL.Path)
  524. http.Error(w, "not found", http.StatusNotFound)
  525. return
  526. }
  527. if tt.expectedError != "" {
  528. http.Error(w, tt.expectedError, http.StatusInternalServerError)
  529. return
  530. }
  531. response := api.ListResponse{Models: tt.serverResponse}
  532. if err := json.NewEncoder(w).Encode(response); err != nil {
  533. t.Fatal(err)
  534. }
  535. }))
  536. defer mockServer.Close()
  537. t.Setenv("OLLAMA_HOST", mockServer.URL)
  538. cmd := &cobra.Command{}
  539. cmd.SetContext(context.TODO())
  540. // Capture stdout
  541. oldStdout := os.Stdout
  542. r, w, _ := os.Pipe()
  543. os.Stdout = w
  544. err := ListHandler(cmd, tt.args)
  545. // Restore stdout and get output
  546. w.Close()
  547. os.Stdout = oldStdout
  548. output, _ := io.ReadAll(r)
  549. if tt.expectedError == "" {
  550. if err != nil {
  551. t.Errorf("expected no error, got %v", err)
  552. }
  553. if got := string(output); got != tt.expectedOutput {
  554. t.Errorf("expected output:\n%s\ngot:\n%s", tt.expectedOutput, got)
  555. }
  556. } else {
  557. if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
  558. t.Errorf("expected error containing %q, got %v", tt.expectedError, err)
  559. }
  560. }
  561. })
  562. }
  563. }
  564. func TestCreateHandler(t *testing.T) {
  565. tests := []struct {
  566. name string
  567. modelName string
  568. modelFile string
  569. serverResponse map[string]func(w http.ResponseWriter, r *http.Request)
  570. expectedError string
  571. expectedOutput string
  572. }{
  573. {
  574. name: "successful create",
  575. modelName: "test-model",
  576. modelFile: "FROM foo",
  577. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  578. "/api/create": func(w http.ResponseWriter, r *http.Request) {
  579. if r.Method != http.MethodPost {
  580. t.Errorf("expected POST request, got %s", r.Method)
  581. }
  582. req := api.CreateRequest{}
  583. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  584. http.Error(w, err.Error(), http.StatusBadRequest)
  585. return
  586. }
  587. if req.Name != "test-model" {
  588. t.Errorf("expected model name 'test-model', got %s", req.Name)
  589. }
  590. if req.From != "foo" {
  591. t.Errorf("expected from 'foo', got %s", req.From)
  592. }
  593. responses := []api.ProgressResponse{
  594. {Status: "using existing layer sha256:56bb8bd477a519ffa694fc449c2413c6f0e1d3b1c88fa7e3c9d88d3ae49d4dcb"},
  595. {Status: "writing manifest"},
  596. {Status: "success"},
  597. }
  598. for _, resp := range responses {
  599. if err := json.NewEncoder(w).Encode(resp); err != nil {
  600. http.Error(w, err.Error(), http.StatusInternalServerError)
  601. return
  602. }
  603. w.(http.Flusher).Flush()
  604. }
  605. },
  606. },
  607. expectedOutput: "",
  608. },
  609. }
  610. for _, tt := range tests {
  611. t.Run(tt.name, func(t *testing.T) {
  612. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  613. handler, ok := tt.serverResponse[r.URL.Path]
  614. if !ok {
  615. t.Errorf("unexpected request to %s", r.URL.Path)
  616. http.Error(w, "not found", http.StatusNotFound)
  617. return
  618. }
  619. handler(w, r)
  620. }))
  621. t.Setenv("OLLAMA_HOST", mockServer.URL)
  622. t.Cleanup(mockServer.Close)
  623. tempFile, err := os.CreateTemp("", "modelfile")
  624. if err != nil {
  625. t.Fatal(err)
  626. }
  627. defer os.Remove(tempFile.Name())
  628. if _, err := tempFile.WriteString(tt.modelFile); err != nil {
  629. t.Fatal(err)
  630. }
  631. if err := tempFile.Close(); err != nil {
  632. t.Fatal(err)
  633. }
  634. cmd := &cobra.Command{}
  635. cmd.Flags().String("file", "", "")
  636. if err := cmd.Flags().Set("file", tempFile.Name()); err != nil {
  637. t.Fatal(err)
  638. }
  639. cmd.Flags().Bool("insecure", false, "")
  640. cmd.SetContext(context.TODO())
  641. // Redirect stderr to capture progress output
  642. oldStderr := os.Stderr
  643. r, w, _ := os.Pipe()
  644. os.Stderr = w
  645. // Capture stdout for the "Model pushed" message
  646. oldStdout := os.Stdout
  647. outR, outW, _ := os.Pipe()
  648. os.Stdout = outW
  649. err = CreateHandler(cmd, []string{tt.modelName})
  650. // Restore stderr
  651. w.Close()
  652. os.Stderr = oldStderr
  653. // drain the pipe
  654. if _, err := io.ReadAll(r); err != nil {
  655. t.Fatal(err)
  656. }
  657. // Restore stdout and get output
  658. outW.Close()
  659. os.Stdout = oldStdout
  660. stdout, _ := io.ReadAll(outR)
  661. if tt.expectedError == "" {
  662. if err != nil {
  663. t.Errorf("expected no error, got %v", err)
  664. }
  665. if tt.expectedOutput != "" {
  666. if got := string(stdout); got != tt.expectedOutput {
  667. t.Errorf("expected output %q, got %q", tt.expectedOutput, got)
  668. }
  669. }
  670. }
  671. })
  672. }
  673. }