/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"reflect"
	"testing"
)

func TestConformance(t *testing.T) {
	for _, tc := range []struct {
		desc        string
		filename    string
		code        string
		targetFrame frame
		output      *conformanceData
	}{
		{
			desc:     "Grabs comment above test",
			filename: "test/list/main_test.go",
			code: `package test

	var num = 3
	func Helper(x int) { return x / 0 }
	var _ = Describe("Feature", func() {
	/*
	   Testname: Kubelet-OutputToLogs
	   Description: By default the stdout and stderr from the process
	   being executed in a pod MUST be sent to the pod's logs.
	*/
	 framework.ConformanceIt("validates describe with ConformanceIt", func() {})
	})`,
			output: &conformanceData{
				URL:         "https://github.com/kubernetes/kubernetes/tree/master/test/list/main_test.go#L11",
				TestName:    "Kubelet-OutputToLogs",
				Description: `By default the stdout and stderr from the process being executed in a pod MUST be sent to the pod's logs.`,
				File:        "test/list/main_test.go",
			},
			targetFrame: frame{File: "test/list/main_test.go", Line: 11},
		}, {
			desc:     "Handles extra spaces",
			filename: "e2e/foo.go",
			code: `package test

	var _ = framework.KubeDescribe("Feature", func() {
		   Context("with context and extra spaces before It block should still pick up Testname", func() {
				   //                                      Testname: Test with spaces
				   //Description: Should pick up testname even if it is not within 3 spaces
				   //even when executed from memory.
				   framework.ConformanceIt("should work", func() {})
		   })
	})`,
			output: &conformanceData{
				URL:         "https://github.com/kubernetes/kubernetes/tree/master/e2e/foo.go#L8",
				TestName:    "Test with spaces",
				Description: `Should pick up testname even if it is not within 3 spaces even when executed from memory.`,
				File:        "e2e/foo.go",
			},
			targetFrame: frame{File: "e2e/foo.go", Line: 8},
		}, {
			desc:     "Should target the correct comment based on the line numbers (second)",
			filename: "e2e/foo.go",
			code: `package test

	var _ = framework.KubeDescribe("Feature", func() {
		   Context("with context and extra spaces before It block should still pick up Testname", func() {
				   // Testname: First test
				   // Description: Should pick up testname even if it is not within 3 spaces
				   // even when executed from memory.
				   framework.ConformanceIt("should work", func() {})

				   // Testname: Second test
				   // Description: Should target the correct test/comment based on the line numbers
				   framework.ConformanceIt("should work", func() {})
		   })
	})`,
			output: &conformanceData{
				URL:         "https://github.com/kubernetes/kubernetes/tree/master/e2e/foo.go#L13",
				TestName:    "Second test",
				Description: `Should target the correct test/comment based on the line numbers`,
				File:        "e2e/foo.go",
			},
			targetFrame: frame{File: "e2e/foo.go", Line: 13},
		}, {
			desc:     "Should target the correct comment based on the line numbers (first)",
			filename: "e2e/foo.go",
			code: `package test

	var _ = framework.KubeDescribe("Feature", func() {
		   Context("with context and extra spaces before It block should still pick up Testname", func() {
				   // Testname: First test
				   // Description: Should target the correct test/comment based on the line numbers
				   framework.ConformanceIt("should work", func() {})

				   // Testname: Second test
				   // Description: Should target the correct test/comment based on the line numbers
				   framework.ConformanceIt("should work", func() {})
		   })
	})`,
			output: &conformanceData{
				URL:         "https://github.com/kubernetes/kubernetes/tree/master/e2e/foo.go#L8",
				TestName:    "First test",
				Description: `Should target the correct test/comment based on the line numbers`,
				File:        "e2e/foo.go",
			},
			targetFrame: frame{File: "e2e/foo.go", Line: 8},
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			*confDoc = true
			cd, err := scanFileForFrame(tc.filename, tc.code, tc.targetFrame)
			if err != nil {
				panic(err)
			}
			if !reflect.DeepEqual(cd, tc.output) {
				t.Errorf("code:\n%s\ngot  %+v\nwant %+v",
					tc.code, cd, tc.output)
			}
		})
	}
}

func TestCommentToConformanceData(t *testing.T) {
	tcs := []struct {
		desc     string
		input    string
		expected *conformanceData
	}{
		{
			desc: "Empty comment leads to nil",
		}, {
			desc:  "No Release or Testname leads to nil",
			input: "Description: foo",
		}, {
			desc:     "Release but no Testname does not result in nil",
			input:    "Release: v1.1\nDescription: foo",
			expected: &conformanceData{Release: "v1.1", Description: "foo"},
		}, {
			desc:     "Testname but no Release does not result in nil",
			input:    "Testname: mytest\nDescription: foo",
			expected: &conformanceData{TestName: "mytest", Description: "foo"},
		}, {
			desc:     "All fields parsed and newlines and whitespace removed from description",
			input:    "Release: v1.1\n\t\tTestname: mytest\n\t\tDescription: foo\n\t\tbar\ndone",
			expected: &conformanceData{TestName: "mytest", Release: "v1.1", Description: "foo bar done"},
		},
	}

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			out := commentToConformanceData(tc.input)
			if !reflect.DeepEqual(out, tc.expected) {
				t.Errorf("Expected %#v but got %#v", tc.expected, out)
			}
		})
	}
}

func TestValidateTestName(t *testing.T) {
	testCases := []struct {
		testName  string
		tagString string
	}{
		{
			"a test case with no tags",
			"",
		},
		{
			"a test case with valid tags [LinuxOnly] [NodeConformance] [Serial] [Disruptive]",
			"",
		},
		{
			"a flaky test case that is invalid [Flaky]",
			"[Flaky]",
		},
		{
			"a feature test case that is invalid [Feature:Awesome]",
			"[Feature:Awesome]",
		},
		{
			"an alpha test case that is invalid [Alpha]",
			"[Alpha]",
		},
		{
			"a test case with multiple invalid tags [Flaky] [Feature:Awesome] [Alpha]",
			"[Flaky],[Feature:Awesome],[Alpha]",
		},
		{
			"[sig-awesome] [Alpha] [Disruptive] a test case with valid and invalid tags [Serial] [Flaky]",
			"[Alpha],[Flaky]",
		},
	}
	for i, tc := range testCases {
		err := validateTestName(tc.testName)
		if err != nil {
			if tc.tagString == "" {
				t.Errorf("test case[%d]: expected no validate error, got %q", i, err.Error())
			} else {
				expectedMsg := fmt.Sprintf("'%s' cannot have invalid tags %s", tc.testName, tc.tagString)
				actualMsg := err.Error()
				if actualMsg != expectedMsg {
					t.Errorf("test case[%d]: expected error message %q, got %q", i, expectedMsg, actualMsg)
				}
			}
		}
	}
}
