cmd_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. package cmd
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "testing"
  12. "github.com/google/go-cmp/cmp"
  13. "github.com/spf13/cobra"
  14. "github.com/ollama/ollama/api"
  15. )
  16. func TestShowInfo(t *testing.T) {
  17. t.Run("bare details", func(t *testing.T) {
  18. var b bytes.Buffer
  19. if err := showInfo(&api.ShowResponse{
  20. Details: api.ModelDetails{
  21. Family: "test",
  22. ParameterSize: "7B",
  23. QuantizationLevel: "FP16",
  24. },
  25. }, &b); err != nil {
  26. t.Fatal(err)
  27. }
  28. expect := ` Model
  29. architecture test
  30. parameters 7B
  31. quantization FP16
  32. `
  33. if diff := cmp.Diff(expect, b.String()); diff != "" {
  34. t.Errorf("unexpected output (-want +got):\n%s", diff)
  35. }
  36. })
  37. t.Run("bare model info", func(t *testing.T) {
  38. var b bytes.Buffer
  39. if err := showInfo(&api.ShowResponse{
  40. ModelInfo: map[string]any{
  41. "general.architecture": "test",
  42. "general.parameter_count": float64(7_000_000_000),
  43. "test.context_length": float64(0),
  44. "test.embedding_length": float64(0),
  45. },
  46. Details: api.ModelDetails{
  47. Family: "test",
  48. ParameterSize: "7B",
  49. QuantizationLevel: "FP16",
  50. },
  51. }, &b); err != nil {
  52. t.Fatal(err)
  53. }
  54. expect := ` Model
  55. architecture test
  56. parameters 7B
  57. context length 0
  58. embedding length 0
  59. quantization FP16
  60. `
  61. if diff := cmp.Diff(expect, b.String()); diff != "" {
  62. t.Errorf("unexpected output (-want +got):\n%s", diff)
  63. }
  64. })
  65. t.Run("parameters", func(t *testing.T) {
  66. var b bytes.Buffer
  67. if err := showInfo(&api.ShowResponse{
  68. Details: api.ModelDetails{
  69. Family: "test",
  70. ParameterSize: "7B",
  71. QuantizationLevel: "FP16",
  72. },
  73. Parameters: `
  74. stop never
  75. stop gonna
  76. stop give
  77. stop you
  78. stop up
  79. temperature 99`,
  80. }, &b); err != nil {
  81. t.Fatal(err)
  82. }
  83. expect := ` Model
  84. architecture test
  85. parameters 7B
  86. quantization FP16
  87. Parameters
  88. stop never
  89. stop gonna
  90. stop give
  91. stop you
  92. stop up
  93. temperature 99
  94. `
  95. if diff := cmp.Diff(expect, b.String()); diff != "" {
  96. t.Errorf("unexpected output (-want +got):\n%s", diff)
  97. }
  98. })
  99. t.Run("project info", func(t *testing.T) {
  100. var b bytes.Buffer
  101. if err := showInfo(&api.ShowResponse{
  102. Details: api.ModelDetails{
  103. Family: "test",
  104. ParameterSize: "7B",
  105. QuantizationLevel: "FP16",
  106. },
  107. ProjectorInfo: map[string]any{
  108. "general.architecture": "clip",
  109. "general.parameter_count": float64(133_700_000),
  110. "clip.vision.embedding_length": float64(0),
  111. "clip.vision.projection_dim": float64(0),
  112. },
  113. }, &b); err != nil {
  114. t.Fatal(err)
  115. }
  116. expect := ` Model
  117. architecture test
  118. parameters 7B
  119. quantization FP16
  120. Projector
  121. architecture clip
  122. parameters 133.70M
  123. embedding length 0
  124. dimensions 0
  125. `
  126. if diff := cmp.Diff(expect, b.String()); diff != "" {
  127. t.Errorf("unexpected output (-want +got):\n%s", diff)
  128. }
  129. })
  130. t.Run("system", func(t *testing.T) {
  131. var b bytes.Buffer
  132. if err := showInfo(&api.ShowResponse{
  133. Details: api.ModelDetails{
  134. Family: "test",
  135. ParameterSize: "7B",
  136. QuantizationLevel: "FP16",
  137. },
  138. System: `You are a pirate!
  139. Ahoy, matey!
  140. Weigh anchor!
  141. `,
  142. }, &b); err != nil {
  143. t.Fatal(err)
  144. }
  145. expect := ` Model
  146. architecture test
  147. parameters 7B
  148. quantization FP16
  149. System
  150. You are a pirate!
  151. Ahoy, matey!
  152. `
  153. if diff := cmp.Diff(expect, b.String()); diff != "" {
  154. t.Errorf("unexpected output (-want +got):\n%s", diff)
  155. }
  156. })
  157. t.Run("license", func(t *testing.T) {
  158. var b bytes.Buffer
  159. license, err := os.ReadFile(filepath.Join("..", "LICENSE"))
  160. if err != nil {
  161. t.Fatal(err)
  162. }
  163. if err := showInfo(&api.ShowResponse{
  164. Details: api.ModelDetails{
  165. Family: "test",
  166. ParameterSize: "7B",
  167. QuantizationLevel: "FP16",
  168. },
  169. License: string(license),
  170. }, &b); err != nil {
  171. t.Fatal(err)
  172. }
  173. expect := ` Model
  174. architecture test
  175. parameters 7B
  176. quantization FP16
  177. License
  178. MIT License
  179. Copyright (c) Ollama
  180. `
  181. if diff := cmp.Diff(expect, b.String()); diff != "" {
  182. t.Errorf("unexpected output (-want +got):\n%s", diff)
  183. }
  184. })
  185. }
  186. func TestDeleteHandler(t *testing.T) {
  187. stopped := false
  188. mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  189. if r.URL.Path == "/api/delete" && r.Method == http.MethodDelete {
  190. var req api.DeleteRequest
  191. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  192. http.Error(w, err.Error(), http.StatusBadRequest)
  193. return
  194. }
  195. if req.Name == "test-model" {
  196. w.WriteHeader(http.StatusOK)
  197. } else {
  198. w.WriteHeader(http.StatusNotFound)
  199. }
  200. return
  201. }
  202. if r.URL.Path == "/api/generate" && r.Method == http.MethodPost {
  203. var req api.GenerateRequest
  204. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  205. http.Error(w, err.Error(), http.StatusBadRequest)
  206. return
  207. }
  208. if req.Model == "test-model" {
  209. w.WriteHeader(http.StatusOK)
  210. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  211. Done: true,
  212. }); err != nil {
  213. http.Error(w, err.Error(), http.StatusInternalServerError)
  214. }
  215. stopped = true
  216. return
  217. } else {
  218. w.WriteHeader(http.StatusNotFound)
  219. if err := json.NewEncoder(w).Encode(api.GenerateResponse{
  220. Done: false,
  221. }); err != nil {
  222. http.Error(w, err.Error(), http.StatusInternalServerError)
  223. }
  224. }
  225. }
  226. }))
  227. t.Setenv("OLLAMA_HOST", mockServer.URL)
  228. t.Cleanup(mockServer.Close)
  229. cmd := &cobra.Command{}
  230. cmd.SetContext(context.TODO())
  231. if err := DeleteHandler(cmd, []string{"test-model"}); err != nil {
  232. t.Fatalf("DeleteHandler failed: %v", err)
  233. }
  234. if !stopped {
  235. t.Fatal("Model was not stopped before deletion")
  236. }
  237. err := DeleteHandler(cmd, []string{"test-model-not-found"})
  238. if err == nil || !strings.Contains(err.Error(), "unable to stop existing running model \"test-model-not-found\"") {
  239. t.Fatalf("DeleteHandler failed: expected error about stopping non-existent model, got %v", err)
  240. }
  241. }
  242. func TestGetModelfileName(t *testing.T) {
  243. tests := []struct {
  244. name string
  245. modelfileName string
  246. fileExists bool
  247. expectedName string
  248. expectedErr error
  249. }{
  250. {
  251. name: "no modelfile specified, no modelfile exists",
  252. modelfileName: "",
  253. fileExists: false,
  254. expectedName: "",
  255. expectedErr: os.ErrNotExist,
  256. },
  257. {
  258. name: "no modelfile specified, modelfile exists",
  259. modelfileName: "",
  260. fileExists: true,
  261. expectedName: "Modelfile",
  262. expectedErr: nil,
  263. },
  264. {
  265. name: "modelfile specified, no modelfile exists",
  266. modelfileName: "crazyfile",
  267. fileExists: false,
  268. expectedName: "crazyfile",
  269. expectedErr: os.ErrNotExist,
  270. },
  271. {
  272. name: "modelfile specified, modelfile exists",
  273. modelfileName: "anotherfile",
  274. fileExists: true,
  275. expectedName: "anotherfile",
  276. expectedErr: nil,
  277. },
  278. }
  279. for _, tt := range tests {
  280. t.Run(tt.name, func(t *testing.T) {
  281. cmd := &cobra.Command{
  282. Use: "fakecmd",
  283. }
  284. cmd.Flags().String("file", "", "path to modelfile")
  285. var expectedFilename string
  286. if tt.fileExists {
  287. tempDir, err := os.MkdirTemp("", "modelfiledir")
  288. defer os.RemoveAll(tempDir)
  289. if err != nil {
  290. t.Fatalf("temp modelfile dir creation failed: %v", err)
  291. }
  292. var fn string
  293. if tt.modelfileName != "" {
  294. fn = tt.modelfileName
  295. } else {
  296. fn = "Modelfile"
  297. }
  298. tempFile, err := os.CreateTemp(tempDir, fn)
  299. if err != nil {
  300. t.Fatalf("temp modelfile creation failed: %v", err)
  301. }
  302. expectedFilename = tempFile.Name()
  303. err = cmd.Flags().Set("file", expectedFilename)
  304. if err != nil {
  305. t.Fatalf("couldn't set file flag: %v", err)
  306. }
  307. } else {
  308. if tt.modelfileName != "" {
  309. expectedFilename = tt.modelfileName
  310. err := cmd.Flags().Set("file", tt.modelfileName)
  311. if err != nil {
  312. t.Fatalf("couldn't set file flag: %v", err)
  313. }
  314. }
  315. }
  316. actualFilename, actualErr := getModelfileName(cmd)
  317. if actualFilename != expectedFilename {
  318. t.Errorf("expected filename: '%s' actual filename: '%s'", expectedFilename, actualFilename)
  319. }
  320. if tt.expectedErr != os.ErrNotExist {
  321. if actualErr != tt.expectedErr {
  322. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  323. }
  324. } else {
  325. if !os.IsNotExist(actualErr) {
  326. t.Errorf("expected err: %v actual err: %v", tt.expectedErr, actualErr)
  327. }
  328. }
  329. })
  330. }
  331. }