github.com/release-engineering/exodus-rsync@v1.11.2/internal/args/parse_test.go (about) 1 package args 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 8 "github.com/alecthomas/kong" 9 ) 10 11 func TestParseOk(t *testing.T) { 12 tests := map[string]struct { 13 input []string 14 want Config 15 }{ 16 "trivial": {input: []string{"exodus-rsync", "some-src", "some-dest"}, 17 want: Config{Src: "some-src", Dest: "some-dest"}}, 18 19 "ignored args": { 20 // At least all compound names should be in long-form to ensure rsync compatibility. 21 input: []string{ 22 "exodus-rsync", 23 "-arpEogDtz", 24 "--copy-links", 25 "--keep-dirlinks", 26 "--hard-links", 27 "--acls", 28 "--xattrs", 29 "--atimes", 30 "--crtimes", 31 "--omit-dir-times", 32 "--rsh", "abc", 33 "--delete", 34 "--prune-empty-dirs", 35 "--timeout", "123", 36 "--stats", 37 "--itemize-changes", 38 "x", 39 "y"}, 40 want: Config{Src: "x", Dest: "y", 41 IgnoredConfig: IgnoredConfig{ 42 Archive: true, 43 Recursive: true, 44 CopyLinks: true, 45 KeepDirlinks: true, 46 HardLinks: true, 47 Perms: true, 48 Executability: true, 49 Acls: true, 50 Xattrs: true, 51 Owner: true, 52 Group: true, 53 Devices: true, 54 Specials: true, 55 DevicesSpecials: true, 56 Times: true, 57 Atimes: true, 58 Crtimes: true, 59 OmitDirTimes: true, 60 Rsh: "abc", 61 Delete: true, 62 PruneEmptyDirs: true, 63 Timeout: 123, 64 Compress: true, 65 Stats: true, 66 ItemizeChanges: true, 67 }}}, 68 69 "verbose": { 70 input: []string{ 71 "exodus-rsync", 72 "-vv", "--verbose", 73 "x", 74 "y"}, 75 want: Config{Verbose: 3, Src: "x", Dest: "y"}}, 76 77 "relative": { 78 input: []string{ 79 "exodus-rsync", 80 "--relative", 81 "x", 82 "y"}, 83 want: Config{Relative: true, Src: "x", Dest: "y"}}, 84 85 "links": { 86 input: []string{ 87 "exodus-rsync", 88 "-l", 89 "x", 90 "y"}, 91 want: Config{Links: true, Src: "x", Dest: "y"}}, 92 93 "exclude": { 94 input: []string{ 95 "exodus-rsync", 96 "--exclude", 97 ".*", 98 "--exclude", 99 "*.conf", 100 "x", 101 "y"}, 102 want: Config{Exclude: []string{".*", "*.conf"}, Src: "x", Dest: "y"}}, 103 104 "files-from": { 105 input: []string{ 106 "exodus-rsync", 107 "--files-from", 108 "sources.txt", 109 "x", 110 "y"}, 111 want: Config{FilesFrom: "sources.txt", Src: "x", Dest: "y"}}, 112 113 "tolerable filter": { 114 input: []string{ 115 "exodus-rsync", 116 "--filter", "+ **/hi/**", 117 "--filter=-/_*", 118 "x", 119 "y"}, 120 want: Config{Src: "x", Dest: "y", Filter: []string{"+ **/hi/**", "-/_*"}}}, 121 "with publish": { 122 input: []string{ 123 "exodus-rsync", 124 "--exodus-publish", 125 "3e0a4539-be4a-437e-a45f-6d72f7192f17", 126 "x", 127 "y", 128 }, 129 want: Config{Src: "x", Dest: "y", ExodusConfig: ExodusConfig{Publish: "3e0a4539-be4a-437e-a45f-6d72f7192f17"}}, 130 }, 131 } 132 for name, tc := range tests { 133 t.Run(name, func(t *testing.T) { 134 got := Parse(tc.input, "", nil) 135 if !reflect.DeepEqual(tc.want, got) { 136 t.Fatalf("expected: %v, got: %v", tc.want, got) 137 } 138 }) 139 } 140 } 141 142 func TestParseErrors(t *testing.T) { 143 tests := map[string]struct { 144 input []string 145 }{ 146 "missing src dest": {[]string{"exodus-rsync"}}, 147 148 "bad filter": {[]string{"exodus-rsync", "--filter", "quux", "x", "y"}}, 149 } 150 151 for name, tc := range tests { 152 exitcode := 0 153 154 t.Run(name, func(t *testing.T) { 155 Parse(tc.input, "", func(code int) { 156 exitcode = code 157 }) 158 159 if exitcode == 0 { 160 t.Fatal("should have exited with error, did not") 161 } 162 }) 163 } 164 } 165 166 func TestDestPath(t *testing.T) { 167 tests := []struct { 168 name string 169 src string 170 dest string 171 rel bool 172 want string 173 }{ 174 {"no : in dest", ".", "some-dest", true, ""}, 175 {": in dest", ".", "user@somehost:/some/rsync/path", false, "/some/rsync/path"}, 176 {"relative dest", "/some/path", "user@somehost:/rsync/", true, "/rsync/some/path"}, 177 } 178 for _, tc := range tests { 179 t.Run(tc.name, func(t *testing.T) { 180 c := Config{Src: tc.src, Dest: tc.dest, Relative: tc.rel} 181 if got := c.DestPath(); got != tc.want { 182 t.Errorf("Config.DestPath() = %v, want %v", got, tc.want) 183 } 184 }) 185 } 186 } 187 188 func TestStringMapDecodeError(t *testing.T) { 189 err := argStringMapper{}.Decode( 190 &kong.DecodeContext{Value: &kong.Value{}, Scan: &kong.Scanner{}}, 191 reflect.Value{}, 192 ) 193 194 // Scan holds no tokens; no flag or flag value to decode. 195 if err.Error() != "flag : missing value" { 196 t.Fatalf("didn't get expected error, got %s", err.Error()) 197 } 198 } 199 200 func TestConfigValidationErrors(t *testing.T) { 201 var exitcode int 202 203 config := Parse([]string{"exodus-rsync", "some-src", strings.Repeat("some-dest", 250), "--exodus-publish", "zombies"}, "", func(code int) { 204 exitcode = code 205 }) 206 if exitcode != 0 { 207 t.Fatalf("unexpected parsing error: %d\nconfig: %v", exitcode, config) 208 } 209 210 expected := []string{ 211 "validation error(s):", 212 "Key: 'Config.Dest' Error:Field validation for 'Dest' failed on the 'max' tag", 213 "Key: 'Config.ExodusConfig.Publish' Error:Field validation for 'Publish' failed on the 'uuid' tag", 214 } 215 err := config.ValidateConfig() 216 if err == nil { 217 t.Fatalf("didn't raise a validation error") 218 } 219 for _, str := range expected { 220 if !strings.Contains(err.Error(), str) { 221 t.Fatalf("didn't get expected error, got %s", err.Error()) 222 } 223 } 224 }