mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-29 17:24:20 -04:00 
			
		
		
		
	
		
			
	
	
		
			292 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			292 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | package middleware | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"fmt" | ||
|  | 	"runtime" | ||
|  | 	"strings" | ||
|  | 	"testing" | ||
|  | ) | ||
|  | 
 | ||
|  | func TestParseUnixCommand(t *testing.T) { | ||
|  | 	tests := []struct { | ||
|  | 		input    string | ||
|  | 		expected []string | ||
|  | 	}{ | ||
|  | 		// 0 - emtpy command | ||
|  | 		{ | ||
|  | 			input:    ``, | ||
|  | 			expected: []string{}, | ||
|  | 		}, | ||
|  | 		// 1 - command without arguments | ||
|  | 		{ | ||
|  | 			input:    `command`, | ||
|  | 			expected: []string{`command`}, | ||
|  | 		}, | ||
|  | 		// 2 - command with single argument | ||
|  | 		{ | ||
|  | 			input:    `command arg1`, | ||
|  | 			expected: []string{`command`, `arg1`}, | ||
|  | 		}, | ||
|  | 		// 3 - command with multiple arguments | ||
|  | 		{ | ||
|  | 			input:    `command arg1 arg2`, | ||
|  | 			expected: []string{`command`, `arg1`, `arg2`}, | ||
|  | 		}, | ||
|  | 		// 4 - command with single argument with space character - in quotes | ||
|  | 		{ | ||
|  | 			input:    `command "arg1 arg1"`, | ||
|  | 			expected: []string{`command`, `arg1 arg1`}, | ||
|  | 		}, | ||
|  | 		// 5 - command with multiple spaces and tab character | ||
|  | 		{ | ||
|  | 			input:    "command arg1    arg2\targ3", | ||
|  | 			expected: []string{`command`, `arg1`, `arg2`, `arg3`}, | ||
|  | 		}, | ||
|  | 		// 6 - command with single argument with space character - escaped with backspace | ||
|  | 		{ | ||
|  | 			input:    `command arg1\ arg2`, | ||
|  | 			expected: []string{`command`, `arg1 arg2`}, | ||
|  | 		}, | ||
|  | 		// 7 - single quotes should escape special chars | ||
|  | 		{ | ||
|  | 			input:    `command 'arg1\ arg2'`, | ||
|  | 			expected: []string{`command`, `arg1\ arg2`}, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, test := range tests { | ||
|  | 		errorPrefix := fmt.Sprintf("Test [%d]: ", i) | ||
|  | 		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) | ||
|  | 		actual, _ := parseUnixCommand(test.input) | ||
|  | 		if len(actual) != len(test.expected) { | ||
|  | 			t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		for j := 0; j < len(actual); j++ { | ||
|  | 			if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { | ||
|  | 				t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestParseWindowsCommand(t *testing.T) { | ||
|  | 	tests := []struct { | ||
|  | 		input    string | ||
|  | 		expected []string | ||
|  | 	}{ | ||
|  | 		{ // 0 - empty command - do not fail | ||
|  | 			input:    ``, | ||
|  | 			expected: []string{}, | ||
|  | 		}, | ||
|  | 		{ // 1 - cmd without args | ||
|  | 			input:    `cmd`, | ||
|  | 			expected: []string{`cmd`}, | ||
|  | 		}, | ||
|  | 		{ // 2 - multiple args | ||
|  | 			input:    `cmd arg1 arg2`, | ||
|  | 			expected: []string{`cmd`, `arg1`, `arg2`}, | ||
|  | 		}, | ||
|  | 		{ // 3 - multiple args with space | ||
|  | 			input:    `cmd "combined arg" arg2`, | ||
|  | 			expected: []string{`cmd`, `combined arg`, `arg2`}, | ||
|  | 		}, | ||
|  | 		{ // 4 - path without spaces | ||
|  | 			input:    `mkdir C:\Windows\foo\bar`, | ||
|  | 			expected: []string{`mkdir`, `C:\Windows\foo\bar`}, | ||
|  | 		}, | ||
|  | 		{ // 5 - command with space in quotes | ||
|  | 			input:    `"command here"`, | ||
|  | 			expected: []string{`command here`}, | ||
|  | 		}, | ||
|  | 		{ // 6 - argument with escaped quotes (two quotes) | ||
|  | 			input:    `cmd ""arg""`, | ||
|  | 			expected: []string{`cmd`, `"arg"`}, | ||
|  | 		}, | ||
|  | 		{ // 7 - argument with escaped quotes (backslash) | ||
|  | 			input:    `cmd \"arg\"`, | ||
|  | 			expected: []string{`cmd`, `"arg"`}, | ||
|  | 		}, | ||
|  | 		{ // 8 - two quotes (escaped) inside an inQuote element | ||
|  | 			input:    `cmd "a ""quoted value"`, | ||
|  | 			expected: []string{`cmd`, `a "quoted value`}, | ||
|  | 		}, | ||
|  | 		// TODO - see how many quotes are dislayed if we use "", """, """"""" | ||
|  | 		{ // 9 - two quotes outside an inQuote element | ||
|  | 			input:    `cmd a ""quoted value`, | ||
|  | 			expected: []string{`cmd`, `a`, `"quoted`, `value`}, | ||
|  | 		}, | ||
|  | 		{ // 10 - path with space in quotes | ||
|  | 			input:    `mkdir "C:\directory name\foobar"`, | ||
|  | 			expected: []string{`mkdir`, `C:\directory name\foobar`}, | ||
|  | 		}, | ||
|  | 		{ // 11 - space without quotes | ||
|  | 			input:    `mkdir C:\ space`, | ||
|  | 			expected: []string{`mkdir`, `C:\`, `space`}, | ||
|  | 		}, | ||
|  | 		{ // 12 - space in quotes | ||
|  | 			input:    `mkdir "C:\ space"`, | ||
|  | 			expected: []string{`mkdir`, `C:\ space`}, | ||
|  | 		}, | ||
|  | 		{ // 13 - UNC | ||
|  | 			input:    `mkdir \\?\C:\Users`, | ||
|  | 			expected: []string{`mkdir`, `\\?\C:\Users`}, | ||
|  | 		}, | ||
|  | 		{ // 14 - UNC with space | ||
|  | 			input:    `mkdir "\\?\C:\Program Files"`, | ||
|  | 			expected: []string{`mkdir`, `\\?\C:\Program Files`}, | ||
|  | 		}, | ||
|  | 
 | ||
|  | 		{ // 15 - unclosed quotes - treat as if the path ends with quote | ||
|  | 			input:    `mkdir "c:\Program files`, | ||
|  | 			expected: []string{`mkdir`, `c:\Program files`}, | ||
|  | 		}, | ||
|  | 		{ // 16 - quotes used inside the argument | ||
|  | 			input:    `mkdir "c:\P"rogra"m f"iles`, | ||
|  | 			expected: []string{`mkdir`, `c:\Program files`}, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, test := range tests { | ||
|  | 		errorPrefix := fmt.Sprintf("Test [%d]: ", i) | ||
|  | 		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) | ||
|  | 
 | ||
|  | 		actual := parseWindowsCommand(test.input) | ||
|  | 		if len(actual) != len(test.expected) { | ||
|  | 			t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		for j := 0; j < len(actual); j++ { | ||
|  | 			if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { | ||
|  | 				t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestSplitCommandAndArgs(t *testing.T) { | ||
|  | 
 | ||
|  | 	// force linux parsing. It's more robust and covers error cases | ||
|  | 	runtimeGoos = "linux" | ||
|  | 	defer func() { | ||
|  | 		runtimeGoos = runtime.GOOS | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	var parseErrorContent = "error parsing command:" | ||
|  | 	var noCommandErrContent = "no command contained in" | ||
|  | 
 | ||
|  | 	tests := []struct { | ||
|  | 		input              string | ||
|  | 		expectedCommand    string | ||
|  | 		expectedArgs       []string | ||
|  | 		expectedErrContent string | ||
|  | 	}{ | ||
|  | 		// 0 - emtpy command | ||
|  | 		{ | ||
|  | 			input:              ``, | ||
|  | 			expectedCommand:    ``, | ||
|  | 			expectedArgs:       nil, | ||
|  | 			expectedErrContent: noCommandErrContent, | ||
|  | 		}, | ||
|  | 		// 1 - command without arguments | ||
|  | 		{ | ||
|  | 			input:              `command`, | ||
|  | 			expectedCommand:    `command`, | ||
|  | 			expectedArgs:       nil, | ||
|  | 			expectedErrContent: ``, | ||
|  | 		}, | ||
|  | 		// 2 - command with single argument | ||
|  | 		{ | ||
|  | 			input:              `command arg1`, | ||
|  | 			expectedCommand:    `command`, | ||
|  | 			expectedArgs:       []string{`arg1`}, | ||
|  | 			expectedErrContent: ``, | ||
|  | 		}, | ||
|  | 		// 3 - command with multiple arguments | ||
|  | 		{ | ||
|  | 			input:              `command arg1 arg2`, | ||
|  | 			expectedCommand:    `command`, | ||
|  | 			expectedArgs:       []string{`arg1`, `arg2`}, | ||
|  | 			expectedErrContent: ``, | ||
|  | 		}, | ||
|  | 		// 4 - command with unclosed quotes | ||
|  | 		{ | ||
|  | 			input:              `command "arg1 arg2`, | ||
|  | 			expectedCommand:    "", | ||
|  | 			expectedArgs:       nil, | ||
|  | 			expectedErrContent: parseErrorContent, | ||
|  | 		}, | ||
|  | 		// 5 - command with unclosed quotes | ||
|  | 		{ | ||
|  | 			input:              `command 'arg1 arg2"`, | ||
|  | 			expectedCommand:    "", | ||
|  | 			expectedArgs:       nil, | ||
|  | 			expectedErrContent: parseErrorContent, | ||
|  | 		}, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for i, test := range tests { | ||
|  | 		errorPrefix := fmt.Sprintf("Test [%d]: ", i) | ||
|  | 		errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) | ||
|  | 		actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input) | ||
|  | 
 | ||
|  | 		// test if error matches expectation | ||
|  | 		if test.expectedErrContent != "" { | ||
|  | 			if actualErr == nil { | ||
|  | 				t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent) | ||
|  | 			} else if !strings.Contains(actualErr.Error(), test.expectedErrContent) { | ||
|  | 				t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr) | ||
|  | 			} | ||
|  | 		} else if actualErr != nil { | ||
|  | 			t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// test if command matches | ||
|  | 		if test.expectedCommand != actualCommand { | ||
|  | 			t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// test if arguments match | ||
|  | 		if len(test.expectedArgs) != len(actualArgs) { | ||
|  | 			t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs) | ||
|  | 		} else { | ||
|  | 			// test args only if the count matches. | ||
|  | 			for j, actualArg := range actualArgs { | ||
|  | 				expectedArg := test.expectedArgs[j] | ||
|  | 				if actualArg != expectedArg { | ||
|  | 					t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg) | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func ExampleSplitCommandAndArgs() { | ||
|  | 	var commandLine string | ||
|  | 	var command string | ||
|  | 	var args []string | ||
|  | 
 | ||
|  | 	// just for the test - change GOOS and reset it at the end of the test | ||
|  | 	runtimeGoos = "windows" | ||
|  | 	defer func() { | ||
|  | 		runtimeGoos = runtime.GOOS | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	commandLine = `mkdir /P "C:\Program Files"` | ||
|  | 	command, args, _ = SplitCommandAndArgs(commandLine) | ||
|  | 
 | ||
|  | 	fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) | ||
|  | 
 | ||
|  | 	// set GOOS to linux | ||
|  | 	runtimeGoos = "linux" | ||
|  | 
 | ||
|  | 	commandLine = `mkdir -p /path/with\ space` | ||
|  | 	command, args, _ = SplitCommandAndArgs(commandLine) | ||
|  | 
 | ||
|  | 	fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) | ||
|  | 
 | ||
|  | 	// Output: | ||
|  | 	// Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files] | ||
|  | 	// Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space] | ||
|  | } |