1 package sync
2
3 import (
4 "context"
5 "errors"
6 "io"
7 "os"
8 "path"
9 "path/filepath"
10 "testing"
11
12 "github.com/devfile/library/v2/pkg/devfile/generator"
13 "github.com/golang/mock/gomock"
14 "github.com/google/go-cmp/cmp"
15
16 "github.com/redhat-developer/odo/pkg/exec"
17 "github.com/redhat-developer/odo/pkg/kclient"
18 "github.com/redhat-developer/odo/pkg/util"
19 "github.com/redhat-developer/odo/tests/helper"
20 )
21
22 func TestGetCmdToCreateSyncFolder(t *testing.T) {
23 tests := []struct {
24 name string
25 syncFolder string
26 want []string
27 }{
28 {
29 name: "Case 1: Sync to /projects",
30 syncFolder: generator.DevfileSourceVolumeMount,
31 want: []string{"mkdir", "-p", generator.DevfileSourceVolumeMount},
32 },
33 {
34 name: "Case 2: Sync subdir of /projects",
35 syncFolder: generator.DevfileSourceVolumeMount + "/someproject",
36 want: []string{"mkdir", "-p", generator.DevfileSourceVolumeMount + "/someproject"},
37 },
38 }
39 for _, tt := range tests {
40 cmdArr := getCmdToCreateSyncFolder(tt.syncFolder)
41 if diff := cmp.Diff(tt.want, cmdArr); diff != "" {
42 t.Errorf("getCmdToCreateSyncFolder() mismatch (-want +got):\n%s", diff)
43 }
44 }
45 }
46
47 func TestGetCmdToDeleteFiles(t *testing.T) {
48 syncFolder := "/projects/hello-world"
49
50 tests := []struct {
51 name string
52 delFiles []string
53 syncFolder string
54 want []string
55 }{
56 {
57 name: "Case 1: One deleted file",
58 delFiles: []string{"test.txt"},
59 syncFolder: generator.DevfileSourceVolumeMount,
60 want: []string{"rm", "-rf", generator.DevfileSourceVolumeMount + "/test.txt"},
61 },
62 {
63 name: "Case 2: Multiple deleted files, default sync folder",
64 delFiles: []string{"test.txt", "hello.c"},
65 syncFolder: generator.DevfileSourceVolumeMount,
66 want: []string{"rm", "-rf", generator.DevfileSourceVolumeMount + "/test.txt", generator.DevfileSourceVolumeMount + "/hello.c"},
67 },
68 {
69 name: "Case 2: Multiple deleted files, different sync folder",
70 delFiles: []string{"test.txt", "hello.c"},
71 syncFolder: syncFolder,
72 want: []string{"rm", "-rf", syncFolder + "/test.txt", syncFolder + "/hello.c"},
73 },
74 }
75 for _, tt := range tests {
76 cmdArr := getCmdToDeleteFiles(tt.delFiles, tt.syncFolder)
77 if diff := cmp.Diff(tt.want, cmdArr); diff != "" {
78 t.Errorf("getCmdToDeleteFiles() mismatch (-want +got):\n%s", diff)
79 }
80 }
81 }
82
83 func TestSyncFiles(t *testing.T) {
84
85 testComponentName := "test"
86
87
88 directory := t.TempDir()
89
90 jsFile, e := os.Create(filepath.Join(directory, "red.js"))
91 if e != nil {
92 t.Errorf("TestSyncFiles error: error creating temporary file for the indexer: %v", e)
93 }
94
95 ctrl := gomock.NewController(t)
96 kc := kclient.NewMockClientInterface(ctrl)
97 kc.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
98 Return(nil).AnyTimes()
99
100
101 defer ctrl.Finish()
102
103 tests := []struct {
104 name string
105 syncParameters SyncParameters
106 wantErr bool
107 wantIsPushRequired bool
108 }{
109 {
110 name: "Case 1: Component does not exist",
111 syncParameters: SyncParameters{
112 Path: directory,
113 WatchFiles: []string{},
114 WatchDeletedFiles: []string{},
115 IgnoredFiles: []string{},
116 CompInfo: ComponentInfo{
117 ContainerName: "abcd",
118 },
119 ForcePush: true,
120 },
121 wantErr: false,
122 wantIsPushRequired: true,
123 },
124 {
125 name: "Case 2: Component does exist",
126 syncParameters: SyncParameters{
127 Path: directory,
128 WatchFiles: []string{},
129 WatchDeletedFiles: []string{},
130 IgnoredFiles: []string{},
131 CompInfo: ComponentInfo{
132 ContainerName: "abcd",
133 },
134 ForcePush: false,
135 },
136 wantErr: false,
137 wantIsPushRequired: false,
138 },
139 {
140 name: "Case 3: FakeErrorClient error",
141 syncParameters: SyncParameters{
142 Path: directory,
143 WatchFiles: []string{},
144 WatchDeletedFiles: []string{},
145 IgnoredFiles: []string{},
146 CompInfo: ComponentInfo{
147 ContainerName: "abcd",
148 },
149 ForcePush: false,
150 },
151 wantErr: true,
152 wantIsPushRequired: false,
153 },
154 {
155 name: "Case 4: File change",
156 syncParameters: SyncParameters{
157 Path: directory,
158 WatchFiles: []string{path.Join(directory, "test.log")},
159 WatchDeletedFiles: []string{},
160 IgnoredFiles: []string{},
161 CompInfo: ComponentInfo{
162 ComponentName: testComponentName,
163 ContainerName: "abcd",
164 },
165 ForcePush: false,
166 },
167 wantErr: false,
168 wantIsPushRequired: true,
169 },
170 }
171 for _, tt := range tests {
172 t.Run(tt.name, func(t *testing.T) {
173 execClient := exec.NewExecClient(kc)
174 syncAdapter := NewSyncClient(kc, execClient)
175 isPushRequired, err := syncAdapter.SyncFiles(context.Background(), tt.syncParameters)
176 if !tt.wantErr && err != nil {
177 t.Errorf("TestSyncFiles error: unexpected error when syncing files %v", err)
178 } else if !tt.wantErr && isPushRequired != tt.wantIsPushRequired {
179 t.Errorf("TestSyncFiles error: isPushRequired mismatch, wanted: %v, got: %v", tt.wantIsPushRequired, isPushRequired)
180 }
181 })
182 }
183
184 err := jsFile.Close()
185 if err != nil {
186 t.Errorf("TestSyncFiles error: error deleting the temp dir %s, err: %v", directory, err)
187 }
188 }
189
190 func TestPushLocal(t *testing.T) {
191
192 testComponentName := "test"
193
194
195 directory := t.TempDir()
196
197 newFilePath := filepath.Join(directory, "foobar.txt")
198 if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil {
199 t.Errorf("TestPushLocal error: the foobar.txt file was not created: %v", err)
200 }
201
202 ctrl := gomock.NewController(t)
203 kc := kclient.NewMockClientInterface(ctrl)
204 kc.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
205 Return(nil).AnyTimes()
206
207
208 defer ctrl.Finish()
209
210 syncClient := func(ComponentInfo, string, io.Reader) error {
211 return nil
212 }
213
214 errorSyncClient := func(ComponentInfo, string, io.Reader) error {
215 return errors.New("err")
216 }
217
218 tests := []struct {
219 name string
220 client SyncExtracter
221 path string
222 files []string
223 delFiles []string
224 isForcePush bool
225 compInfo ComponentInfo
226 wantErr bool
227 }{
228 {
229 name: "Case 1: File change",
230 client: syncClient,
231 path: directory,
232 files: []string{path.Join(directory, "test.log")},
233 delFiles: []string{},
234 isForcePush: false,
235 compInfo: ComponentInfo{
236 ContainerName: "abcd",
237 },
238 wantErr: false,
239 },
240 {
241 name: "Case 2: File change with fake error client",
242 client: errorSyncClient,
243 path: directory,
244 files: []string{path.Join(directory, "test.log")},
245 delFiles: []string{},
246 isForcePush: false,
247 compInfo: ComponentInfo{
248 ContainerName: "abcd",
249 },
250 wantErr: true,
251 },
252 {
253 name: "Case 3: No file change",
254 client: syncClient,
255 path: directory,
256 files: []string{},
257 delFiles: []string{},
258 isForcePush: false,
259 compInfo: ComponentInfo{
260 ContainerName: "abcd",
261 },
262 wantErr: false,
263 },
264 {
265 name: "Case 4: Deleted file",
266 client: syncClient,
267 path: directory,
268 files: []string{},
269 delFiles: []string{path.Join(directory, "test.log")},
270 isForcePush: false,
271 compInfo: ComponentInfo{
272 ContainerName: "abcd",
273 },
274 wantErr: false,
275 },
276 {
277 name: "Case 5: Force push",
278 client: syncClient,
279 path: directory,
280 files: []string{},
281 delFiles: []string{},
282 isForcePush: true,
283 compInfo: ComponentInfo{
284 ContainerName: "abcd",
285 },
286 wantErr: false,
287 },
288 {
289 name: "Case 6: Source mapping folder set",
290 client: syncClient,
291 path: directory,
292 files: []string{},
293 delFiles: []string{},
294 isForcePush: false,
295 compInfo: ComponentInfo{
296 ComponentName: testComponentName,
297 ContainerName: "abcd",
298 SyncFolder: "/some/path",
299 },
300 wantErr: false,
301 },
302 }
303 for _, tt := range tests {
304 t.Run(tt.name, func(t *testing.T) {
305 execClient := exec.NewExecClient(kc)
306 syncAdapter := NewSyncClient(kc, execClient)
307 err := syncAdapter.pushLocal(context.Background(), tt.path, tt.files, tt.delFiles, tt.isForcePush, []string{}, tt.compInfo, util.IndexerRet{})
308 if !tt.wantErr && err != nil {
309 t.Errorf("TestPushLocal error: error pushing files: %v", err)
310 }
311
312 })
313 }
314 }
315
316 func TestUpdateIndexWithWatchChanges(t *testing.T) {
317
318 tests := []struct {
319 name string
320 initialFilesToCreate []string
321 watchDeletedFiles []string
322 watchAddedFiles []string
323 expectedFilesInIndex []string
324 }{
325 {
326 name: "Case 1 - Watch file deleted should remove file from index",
327 initialFilesToCreate: []string{"file1", "file2"},
328 watchDeletedFiles: []string{"file1"},
329 expectedFilesInIndex: []string{"file2"},
330 },
331 {
332 name: "Case 2 - Watch file added should add file to index",
333 initialFilesToCreate: []string{"file1"},
334 watchAddedFiles: []string{"file2"},
335 expectedFilesInIndex: []string{"file1", "file2"},
336 },
337 {
338 name: "Case 3 - No watch changes should mean no index changes",
339 initialFilesToCreate: []string{"file1"},
340 expectedFilesInIndex: []string{"file1"},
341 },
342 }
343 for _, tt := range tests {
344
345
346 directory := t.TempDir()
347
348 fileIndexPath, err := util.ResolveIndexFilePath(directory)
349 if err != nil {
350 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to resolve index file path: %v", err)
351 }
352
353 if err := os.MkdirAll(filepath.Dir(fileIndexPath), 0750); err != nil {
354 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to create directories for %s: %v", fileIndexPath, err)
355 }
356
357 t.Run(tt.name, func(t *testing.T) {
358
359 indexData := map[string]util.FileData{}
360
361
362 for _, fileToCreate := range tt.initialFilesToCreate {
363 filePath := filepath.Join(directory, fileToCreate)
364
365 if err := os.WriteFile(filePath, []byte("non-empty-string"), 0644); err != nil {
366 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to write to index file path: %v", err)
367 }
368
369 key, fileDatum, err := util.GenerateNewFileDataEntry(filePath, directory)
370 if err != nil {
371 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to generate new file: %v", err)
372 }
373 indexData[key] = *fileDatum
374 }
375
376
377 if err := util.WriteFile(indexData, fileIndexPath); err != nil {
378 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to write index file: %v", err)
379 }
380
381 syncParams := SyncParameters{
382 Path: directory,
383 }
384
385
386 for _, deletedFile := range tt.watchDeletedFiles {
387 deletedFilePath := filepath.Join(directory, deletedFile)
388 syncParams.WatchDeletedFiles = append(syncParams.WatchDeletedFiles, deletedFilePath)
389
390 if err := os.Remove(deletedFilePath); err != nil {
391 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to delete file %s %v", deletedFilePath, err)
392 }
393 }
394
395
396 for _, addedFile := range tt.watchAddedFiles {
397 addedFilePath := filepath.Join(directory, addedFile)
398 syncParams.WatchFiles = append(syncParams.WatchFiles, addedFilePath)
399
400 if err := os.WriteFile(addedFilePath, []byte("non-empty-string"), 0644); err != nil {
401 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: unable to write to index file path: %v", err)
402 }
403 }
404
405 if err := updateIndexWithWatchChanges(syncParams); err != nil {
406 t.Fatalf("TestUpdateIndexWithWatchChangesLocal: unexpected error: %v", err)
407 }
408
409 postFileIndex, err := util.ReadFileIndex(fileIndexPath)
410 if err != nil || postFileIndex == nil {
411 t.Fatalf("TestUpdateIndexWithWatchChangesLocal error: read new file index: %v", err)
412 }
413
414
415 if len(postFileIndex.Files) != len(tt.expectedFilesInIndex) {
416 t.Fatalf("Mismatch between number expected files and actual files in index, post-index: %v expected: %v", postFileIndex.Files, tt.expectedFilesInIndex)
417 }
418 for _, expectedFile := range tt.expectedFilesInIndex {
419 if _, exists := postFileIndex.Files[expectedFile]; !exists {
420 t.Fatalf("Unable to find '%s' in post index file, %v", expectedFile, postFileIndex.Files)
421 }
422 }
423 })
424 }
425 }
426
View as plain text