cmd_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  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. "some.true_bool": true,
  80. "some.false_bool": false,
  81. "test.context_length": float64(1000),
  82. "test.embedding_length": float64(11434),
  83. },
  84. Tensors: []api.Tensor{
  85. {Name: "blk.0.attn_k.weight", Type: "BF16", Shape: []uint64{42, 3117}},
  86. {Name: "blk.0.attn_q.weight", Type: "FP16", Shape: []uint64{3117, 42}},
  87. },
  88. }, true, &b); err != nil {
  89. t.Fatal(err)
  90. }
  91. expect := ` Model
  92. architecture test
  93. parameters 8B
  94. context length 1000
  95. embedding length 11434
  96. quantization FP16
  97. Parameters
  98. stop up
  99. Metadata
  100. general.architecture test
  101. general.parameter_count 8e+09
  102. some.false_bool false
  103. some.true_bool true
  104. test.context_length 1000
  105. test.embedding_length 11434
  106. Tensors
  107. blk.0.attn_k.weight BF16 [42 3117]
  108. blk.0.attn_q.weight FP16 [3117 42]
  109. `
  110. if diff := cmp.Diff(expect, b.String()); diff != "" {
  111. t.Errorf("unexpected output (-want +got):\n%s", diff)
  112. }
  113. })
  114. t.Run("parameters", func(t *testing.T) {
  115. var b bytes.Buffer
  116. if err := showInfo(&api.ShowResponse{
  117. Details: api.ModelDetails{
  118. Family: "test",
  119. ParameterSize: "7B",
  120. QuantizationLevel: "FP16",
  121. },
  122. Parameters: `
  123. stop never
  124. stop gonna
  125. stop give
  126. stop you
  127. stop up
  128. temperature 99`,
  129. }, false, &b); err != nil {
  130. t.Fatal(err)
  131. }
  132. expect := ` Model
  133. architecture test
  134. parameters 7B
  135. quantization FP16
  136. Parameters
  137. stop never
  138. stop gonna
  139. stop give
  140. stop you
  141. stop up
  142. temperature 99
  143. `
  144. if diff := cmp.Diff(expect, b.String()); diff != "" {
  145. t.Errorf("unexpected output (-want +got):\n%s", diff)
  146. }
  147. })
  148. t.Run("project info", func(t *testing.T) {
  149. var b bytes.Buffer
  150. if err := showInfo(&api.ShowResponse{
  151. Details: api.ModelDetails{
  152. Family: "test",
  153. ParameterSize: "7B",
  154. QuantizationLevel: "FP16",
  155. },
  156. ProjectorInfo: map[string]any{
  157. "general.architecture": "clip",
  158. "general.parameter_count": float64(133_700_000),
  159. "clip.vision.embedding_length": float64(0),
  160. "clip.vision.projection_dim": float64(0),
  161. },
  162. }, false, &b); err != nil {
  163. t.Fatal(err)
  164. }
  165. expect := ` Model
  166. architecture test
  167. parameters 7B
  168. quantization FP16
  169. Projector
  170. architecture clip
  171. parameters 133.70M
  172. embedding length 0
  173. dimensions 0
  174. `
  175. if diff := cmp.Diff(expect, b.String()); diff != "" {
  176. t.Errorf("unexpected output (-want +got):\n%s", diff)
  177. }
  178. })
  179. t.Run("system", func(t *testing.T) {
  180. var b bytes.Buffer
  181. if err := showInfo(&api.ShowResponse{
  182. Details: api.ModelDetails{
  183. Family: "test",
  184. ParameterSize: "7B",
  185. QuantizationLevel: "FP16",
  186. },
  187. System: `You are a pirate!
  188. Ahoy, matey!
  189. Weigh anchor!
  190. `,
  191. }, false, &b); err != nil {
  192. t.Fatal(err)
  193. }
  194. expect := ` Model
  195. architecture test
  196. parameters 7B
  197. quantization FP16
  198. System
  199. You are a pirate!
  200. Ahoy, matey!
  201. `
  202. if diff := cmp.Diff(expect, b.String()); diff != "" {
  203. t.Errorf("unexpected output (-want +got):\n%s", diff)
  204. }
  205. })
  206. t.Run("license", func(t *testing.T) {
  207. var b bytes.Buffer
  208. license := "MIT License\nCopyright (c) Ollama\n"
  209. if err := showInfo(&api.ShowResponse{
  210. Details: api.ModelDetails{
  211. Family: "test",
  212. ParameterSize: "7B",
  213. QuantizationLevel: "FP16",
  214. },
  215. License: license,
  216. }, false, &b); err != nil {
  217. t.Fatal(err)
  218. }
  219. expect := ` Model
  220. architecture test
  221. parameters 7B
  222. quantization FP16
  223. License
  224. MIT License
  225. Copyright (c) Ollama
  226. `
  227. if diff := cmp.Diff(expect, b.String()); diff != "" {
  228. t.Errorf("unexpected output (-want +got):\n%s", diff)
  229. }
  230. })
  231. }
  232. func TestDeleteHandler(t *testing.T) {
  233. stopped := false
  234. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  235. if r.URL.Path == "/api/delete" && r.Method == http.MethodDelete {
  236. var req api.DeleteRequest
  237. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  238. http.Error(w, err.Error(), http.StatusBadRequest)
  239. return
  240. }
  241. if req.Name == "test-model" {
  242. w.WriteHeader(http.StatusOK)
  243. } else {
  244. w.WriteHeader(http.StatusNotFound)
  245. }
  246. return
  247. }
  248. if r.URL.Path == "/api/generate" && r.Method == http.MethodPost {
  249. var req api.GenerateRequest
  250. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  251. http.Error(w, err.Error(), http.StatusBadRequest)
  252. return
  253. }
  254. if req.Model == "test-model" {
  255. w.WriteHeader(http.StatusOK)
  256. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  257. Done: true,
  258. }); err != nil {
  259. http.Error(w, err.Error(), http.StatusInternalServerError)
  260. }
  261. stopped = true
  262. return
  263. } else {
  264. w.WriteHeader(http.StatusNotFound)
  265. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  266. Done: false,
  267. }); err != nil {
  268. http.Error(w, err.Error(), http.StatusInternalServerError)
  269. }
  270. }
  271. }
  272. }))
  273. t.Setenv("OLLAMA_HOST", mockServer.URL)
  274. t.Cleanup(mockServer.Close)
  275. cmd := &cobra.Command{}
  276. cmd.SetContext(context.TODO())
  277. if err := DeleteHandler(cmd, []string{"test-model"}); err != nil {
  278. t.Fatalf("DeleteHandler failed: %v", err)
  279. }
  280. if !stopped {
  281. t.Fatal("Model was not stopped before deletion")
  282. }
  283. err := DeleteHandler(cmd, []string{"test-model-not-found"})
  284. if err == nil || !strings.Contains(err.Error(), "unable to stop existing running model \"test-model-not-found\"") {
  285. t.Fatalf("DeleteHandler failed: expected error about stopping non-existent model, got %v", err)
  286. }
  287. }
  288. func TestGetModelfileName(t *testing.T) {
  289. tests := []struct {
  290. name string
  291. modelfileName string
  292. fileExists bool
  293. expectedName string
  294. expectedErr error
  295. }{
  296. {
  297. name: "no modelfile specified, no modelfile exists",
  298. modelfileName: "",
  299. fileExists: false,
  300. expectedName: "",
  301. expectedErr: os.ErrNotExist,
  302. },
  303. {
  304. name: "no modelfile specified, modelfile exists",
  305. modelfileName: "",
  306. fileExists: true,
  307. expectedName: "Modelfile",
  308. expectedErr: nil,
  309. },
  310. {
  311. name: "modelfile specified, no modelfile exists",
  312. modelfileName: "crazyfile",
  313. fileExists: false,
  314. expectedName: "",
  315. expectedErr: os.ErrNotExist,
  316. },
  317. {
  318. name: "modelfile specified, modelfile exists",
  319. modelfileName: "anotherfile",
  320. fileExists: true,
  321. expectedName: "anotherfile",
  322. expectedErr: nil,
  323. },
  324. }
  325. for _, tt := range tests {
  326. t.Run(tt.name, func(t *testing.T) {
  327. cmd := &cobra.Command{
  328. Use: "fakecmd",
  329. }
  330. cmd.Flags().String("file", "", "path to modelfile")
  331. var expectedFilename string
  332. if tt.fileExists {
  333. tempDir, err := os.MkdirTemp("", "modelfiledir")
  334. defer os.RemoveAll(tempDir)
  335. if err != nil {
  336. t.Fatalf("temp modelfile dir creation failed: %v", err)
  337. }
  338. var fn string
  339. if tt.modelfileName != "" {
  340. fn = tt.modelfileName
  341. } else {
  342. fn = "Modelfile"
  343. }
  344. tempFile, err := os.CreateTemp(tempDir, fn)
  345. if err != nil {
  346. t.Fatalf("temp modelfile creation failed: %v", err)
  347. }
  348. expectedFilename = tempFile.Name()
  349. err = cmd.Flags().Set("file", expectedFilename)
  350. if err != nil {
  351. t.Fatalf("couldn't set file flag: %v", err)
  352. }
  353. } else {
  354. expectedFilename = tt.expectedName
  355. if tt.modelfileName != "" {
  356. err := cmd.Flags().Set("file", tt.modelfileName)
  357. if err != nil {
  358. t.Fatalf("couldn't set file flag: %v", err)
  359. }
  360. }
  361. }
  362. actualFilename, actualErr := getModelfileName(cmd)
  363. if actualFilename != expectedFilename {
  364. t.Errorf("expected filename: '%s' actual filename: '%s'", expectedFilename, actualFilename)
  365. }
  366. if tt.expectedErr != os.ErrNotExist {
  367. if actualErr != tt.expectedErr {
  368. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  369. }
  370. } else {
  371. if !os.IsNotExist(actualErr) {
  372. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  373. }
  374. }
  375. })
  376. }
  377. }
  378. func TestPushHandler(t *testing.T) {
  379. tests := []struct {
  380. name string
  381. modelName string
  382. serverResponse map[string]func(w http.ResponseWriter, r *http.Request)
  383. expectedError string
  384. expectedOutput string
  385. }{
  386. {
  387. name: "successful push",
  388. modelName: "test-model",
  389. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  390. "/api/push": func(w http.ResponseWriter, r *http.Request) {
  391. if r.Method != http.MethodPost {
  392. t.Errorf("expected POST request, got %s", r.Method)
  393. }
  394. var req api.PushRequest
  395. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  396. http.Error(w, err.Error(), http.StatusBadRequest)
  397. return
  398. }
  399. if req.Name != "test-model" {
  400. t.Errorf("expected model name 'test-model', got %s", req.Name)
  401. }
  402. // Simulate progress updates
  403. responses := []api.ProgressResponse{
  404. {Status: "preparing manifest"},
  405. {Digest: "sha256:abc123456789", Total: 100, Completed: 50},
  406. {Digest: "sha256:abc123456789", Total: 100, Completed: 100},
  407. }
  408. for _, resp := range responses {
  409. if err := json.NewEncoder(w).Encode(resp); err != nil {
  410. http.Error(w, err.Error(), http.StatusInternalServerError)
  411. return
  412. }
  413. w.(http.Flusher).Flush()
  414. }
  415. },
  416. },
  417. expectedOutput: "\nYou can find your model at:\n\n\thttps://ollama.com/test-model\n",
  418. },
  419. {
  420. name: "unauthorized push",
  421. modelName: "unauthorized-model",
  422. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  423. "/api/push": func(w http.ResponseWriter, r *http.Request) {
  424. w.Header().Set("Content-Type", "application/json")
  425. w.WriteHeader(http.StatusUnauthorized)
  426. err := json.NewEncoder(w).Encode(map[string]string{
  427. "error": "access denied",
  428. })
  429. if err != nil {
  430. t.Fatal(err)
  431. }
  432. },
  433. },
  434. expectedError: "you are not authorized to push to this namespace, create the model under a namespace you own",
  435. },
  436. }
  437. for _, tt := range tests {
  438. t.Run(tt.name, func(t *testing.T) {
  439. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  440. if handler, ok := tt.serverResponse[r.URL.Path]; ok {
  441. handler(w, r)
  442. return
  443. }
  444. http.Error(w, "not found", http.StatusNotFound)
  445. }))
  446. defer mockServer.Close()
  447. t.Setenv("OLLAMA_HOST", mockServer.URL)
  448. cmd := &cobra.Command{}
  449. cmd.Flags().Bool("insecure", false, "")
  450. cmd.SetContext(context.TODO())
  451. // Redirect stderr to capture progress output
  452. oldStderr := os.Stderr
  453. r, w, _ := os.Pipe()
  454. os.Stderr = w
  455. // Capture stdout for the "Model pushed" message
  456. oldStdout := os.Stdout
  457. outR, outW, _ := os.Pipe()
  458. os.Stdout = outW
  459. err := PushHandler(cmd, []string{tt.modelName})
  460. // Restore stderr
  461. w.Close()
  462. os.Stderr = oldStderr
  463. // drain the pipe
  464. if _, err := io.ReadAll(r); err != nil {
  465. t.Fatal(err)
  466. }
  467. // Restore stdout and get output
  468. outW.Close()
  469. os.Stdout = oldStdout
  470. stdout, _ := io.ReadAll(outR)
  471. if tt.expectedError == "" {
  472. if err != nil {
  473. t.Errorf("expected no error, got %v", err)
  474. }
  475. if tt.expectedOutput != "" {
  476. if got := string(stdout); got != tt.expectedOutput {
  477. t.Errorf("expected output %q, got %q", tt.expectedOutput, got)
  478. }
  479. }
  480. } else {
  481. if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
  482. t.Errorf("expected error containing %q, got %v", tt.expectedError, err)
  483. }
  484. }
  485. })
  486. }
  487. }
  488. func TestListHandler(t *testing.T) {
  489. tests := []struct {
  490. name string
  491. args []string
  492. serverResponse []api.ListModelResponse
  493. expectedError string
  494. expectedOutput string
  495. }{
  496. {
  497. name: "list all models",
  498. args: []string{},
  499. serverResponse: []api.ListModelResponse{
  500. {Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  501. {Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-48 * time.Hour)},
  502. },
  503. expectedOutput: "NAME ID SIZE MODIFIED \n" +
  504. "model1 sha256:abc12 1.0 KB 24 hours ago \n" +
  505. "model2 sha256:def45 2.0 KB 2 days ago \n",
  506. },
  507. {
  508. name: "filter models by prefix",
  509. args: []string{"model1"},
  510. serverResponse: []api.ListModelResponse{
  511. {Name: "model1", Digest: "sha256:abc123", Size: 1024, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  512. {Name: "model2", Digest: "sha256:def456", Size: 2048, ModifiedAt: time.Now().Add(-24 * time.Hour)},
  513. },
  514. expectedOutput: "NAME ID SIZE MODIFIED \n" +
  515. "model1 sha256:abc12 1.0 KB 24 hours ago \n",
  516. },
  517. {
  518. name: "server error",
  519. args: []string{},
  520. expectedError: "server error",
  521. },
  522. }
  523. for _, tt := range tests {
  524. t.Run(tt.name, func(t *testing.T) {
  525. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  526. if r.URL.Path != "/api/tags" || r.Method != http.MethodGet {
  527. t.Errorf("unexpected request to %s %s", r.Method, r.URL.Path)
  528. http.Error(w, "not found", http.StatusNotFound)
  529. return
  530. }
  531. if tt.expectedError != "" {
  532. http.Error(w, tt.expectedError, http.StatusInternalServerError)
  533. return
  534. }
  535. response := api.ListResponse{Models: tt.serverResponse}
  536. if err := json.NewEncoder(w).Encode(response); err != nil {
  537. t.Fatal(err)
  538. }
  539. }))
  540. defer mockServer.Close()
  541. t.Setenv("OLLAMA_HOST", mockServer.URL)
  542. cmd := &cobra.Command{}
  543. cmd.SetContext(context.TODO())
  544. // Capture stdout
  545. oldStdout := os.Stdout
  546. r, w, _ := os.Pipe()
  547. os.Stdout = w
  548. err := ListHandler(cmd, tt.args)
  549. // Restore stdout and get output
  550. w.Close()
  551. os.Stdout = oldStdout
  552. output, _ := io.ReadAll(r)
  553. if tt.expectedError == "" {
  554. if err != nil {
  555. t.Errorf("expected no error, got %v", err)
  556. }
  557. if got := string(output); got != tt.expectedOutput {
  558. t.Errorf("expected output:\n%s\ngot:\n%s", tt.expectedOutput, got)
  559. }
  560. } else {
  561. if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
  562. t.Errorf("expected error containing %q, got %v", tt.expectedError, err)
  563. }
  564. }
  565. })
  566. }
  567. }
  568. func TestCreateHandler(t *testing.T) {
  569. tests := []struct {
  570. name string
  571. modelName string
  572. modelFile string
  573. serverResponse map[string]func(w http.ResponseWriter, r *http.Request)
  574. expectedError string
  575. expectedOutput string
  576. }{
  577. {
  578. name: "successful create",
  579. modelName: "test-model",
  580. modelFile: "FROM foo",
  581. serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
  582. "/api/create": func(w http.ResponseWriter, r *http.Request) {
  583. if r.Method != http.MethodPost {
  584. t.Errorf("expected POST request, got %s", r.Method)
  585. }
  586. req := api.CreateRequest{}
  587. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  588. http.Error(w, err.Error(), http.StatusBadRequest)
  589. return
  590. }
  591. if req.Name != "test-model" {
  592. t.Errorf("expected model name 'test-model', got %s", req.Name)
  593. }
  594. if req.From != "foo" {
  595. t.Errorf("expected from 'foo', got %s", req.From)
  596. }
  597. responses := []api.ProgressResponse{
  598. {Status: "using existing layer sha256:56bb8bd477a519ffa694fc449c2413c6f0e1d3b1c88fa7e3c9d88d3ae49d4dcb"},
  599. {Status: "writing manifest"},
  600. {Status: "success"},
  601. }
  602. for _, resp := range responses {
  603. if err := json.NewEncoder(w).Encode(resp); err != nil {
  604. http.Error(w, err.Error(), http.StatusInternalServerError)
  605. return
  606. }
  607. w.(http.Flusher).Flush()
  608. }
  609. },
  610. },
  611. expectedOutput: "",
  612. },
  613. }
  614. for _, tt := range tests {
  615. t.Run(tt.name, func(t *testing.T) {
  616. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  617. handler, ok := tt.serverResponse[r.URL.Path]
  618. if !ok {
  619. t.Errorf("unexpected request to %s", r.URL.Path)
  620. http.Error(w, "not found", http.StatusNotFound)
  621. return
  622. }
  623. handler(w, r)
  624. }))
  625. t.Setenv("OLLAMA_HOST", mockServer.URL)
  626. t.Cleanup(mockServer.Close)
  627. tempFile, err := os.CreateTemp("", "modelfile")
  628. if err != nil {
  629. t.Fatal(err)
  630. }
  631. defer os.Remove(tempFile.Name())
  632. if _, err := tempFile.WriteString(tt.modelFile); err != nil {
  633. t.Fatal(err)
  634. }
  635. if err := tempFile.Close(); err != nil {
  636. t.Fatal(err)
  637. }
  638. cmd := &cobra.Command{}
  639. cmd.Flags().String("file", "", "")
  640. if err := cmd.Flags().Set("file", tempFile.Name()); err != nil {
  641. t.Fatal(err)
  642. }
  643. cmd.Flags().Bool("insecure", false, "")
  644. cmd.SetContext(context.TODO())
  645. // Redirect stderr to capture progress output
  646. oldStderr := os.Stderr
  647. r, w, _ := os.Pipe()
  648. os.Stderr = w
  649. // Capture stdout for the "Model pushed" message
  650. oldStdout := os.Stdout
  651. outR, outW, _ := os.Pipe()
  652. os.Stdout = outW
  653. err = CreateHandler(cmd, []string{tt.modelName})
  654. // Restore stderr
  655. w.Close()
  656. os.Stderr = oldStderr
  657. // drain the pipe
  658. if _, err := io.ReadAll(r); err != nil {
  659. t.Fatal(err)
  660. }
  661. // Restore stdout and get output
  662. outW.Close()
  663. os.Stdout = oldStdout
  664. stdout, _ := io.ReadAll(outR)
  665. if tt.expectedError == "" {
  666. if err != nil {
  667. t.Errorf("expected no error, got %v", err)
  668. }
  669. if tt.expectedOutput != "" {
  670. if got := string(stdout); got != tt.expectedOutput {
  671. t.Errorf("expected output %q, got %q", tt.expectedOutput, got)
  672. }
  673. }
  674. }
  675. })
  676. }
  677. }
  678. func TestNewCreateRequest(t *testing.T) {
  679. tests := []struct {
  680. name string
  681. from string
  682. opts runOptions
  683. expected *api.CreateRequest
  684. }{
  685. {
  686. "basic test",
  687. "newmodel",
  688. runOptions{
  689. Model: "mymodel",
  690. ParentModel: "",
  691. Prompt: "You are a fun AI agent",
  692. Messages: []api.Message{},
  693. WordWrap: true,
  694. },
  695. &api.CreateRequest{
  696. From: "mymodel",
  697. Model: "newmodel",
  698. },
  699. },
  700. {
  701. "parent model test",
  702. "newmodel",
  703. runOptions{
  704. Model: "mymodel",
  705. ParentModel: "parentmodel",
  706. Messages: []api.Message{},
  707. WordWrap: true,
  708. },
  709. &api.CreateRequest{
  710. From: "parentmodel",
  711. Model: "newmodel",
  712. },
  713. },
  714. {
  715. "parent model as filepath test",
  716. "newmodel",
  717. runOptions{
  718. Model: "mymodel",
  719. ParentModel: "/some/file/like/etc/passwd",
  720. Messages: []api.Message{},
  721. WordWrap: true,
  722. },
  723. &api.CreateRequest{
  724. From: "mymodel",
  725. Model: "newmodel",
  726. },
  727. },
  728. {
  729. "parent model as windows filepath test",
  730. "newmodel",
  731. runOptions{
  732. Model: "mymodel",
  733. ParentModel: "D:\\some\\file\\like\\etc\\passwd",
  734. Messages: []api.Message{},
  735. WordWrap: true,
  736. },
  737. &api.CreateRequest{
  738. From: "mymodel",
  739. Model: "newmodel",
  740. },
  741. },
  742. {
  743. "options test",
  744. "newmodel",
  745. runOptions{
  746. Model: "mymodel",
  747. ParentModel: "parentmodel",
  748. Options: map[string]any{
  749. "temperature": 1.0,
  750. },
  751. },
  752. &api.CreateRequest{
  753. From: "parentmodel",
  754. Model: "newmodel",
  755. Parameters: map[string]any{
  756. "temperature": 1.0,
  757. },
  758. },
  759. },
  760. {
  761. "messages test",
  762. "newmodel",
  763. runOptions{
  764. Model: "mymodel",
  765. ParentModel: "parentmodel",
  766. System: "You are a fun AI agent",
  767. Messages: []api.Message{
  768. {
  769. Role: "user",
  770. Content: "hello there!",
  771. },
  772. {
  773. Role: "assistant",
  774. Content: "hello to you!",
  775. },
  776. },
  777. WordWrap: true,
  778. },
  779. &api.CreateRequest{
  780. From: "parentmodel",
  781. Model: "newmodel",
  782. System: "You are a fun AI agent",
  783. Messages: []api.Message{
  784. {
  785. Role: "user",
  786. Content: "hello there!",
  787. },
  788. {
  789. Role: "assistant",
  790. Content: "hello to you!",
  791. },
  792. },
  793. },
  794. },
  795. }
  796. for _, tt := range tests {
  797. t.Run(tt.name, func(t *testing.T) {
  798. actual := NewCreateRequest(tt.from, tt.opts)
  799. if !cmp.Equal(actual, tt.expected) {
  800. t.Errorf("expected output %#v, got %#v", tt.expected, actual)
  801. }
  802. })
  803. }
  804. }