Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(refactor) address the e2e extract / refactor of issue #763 #765

Merged
merged 4 commits into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
[cols="1,10,3", options="header", width="100%"]
|===
| | Description | PR

|===

| 🐣
| Refactor `e2e` common code into `lib\test\integration`
maximilien marked this conversation as resolved.
Show resolved Hide resolved
| https://github.com/knative/client/pull/764[#764]
maximilien marked this conversation as resolved.
Show resolved Hide resolved

## v0.13.1 (2020-03-25)

[cols="1,10,3", options="header", width="100%"]
Expand Down
2 changes: 1 addition & 1 deletion hack/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

set -o pipefail

source_dirs="cmd pkg test"
source_dirs="cmd pkg test lib"

# Store for later
if [ -z "$1" ]; then
Expand Down
141 changes: 27 additions & 114 deletions test/e2e/cli.go → lib/test/integration/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,153 +12,64 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e
package integration

import (
"bytes"
"fmt"
"io"
"os/exec"
"strings"
"testing"

"github.com/pkg/errors"
)

type kn struct {
namespace string
}

const (
seperatorHeavy = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
seperatorLight = "╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍"
)

// Run the 'kn' CLI with args and opts
func (k kn) Run(args ...string) KnRunResult {
return RunKn(k.namespace, args)
}

// Helper methods for calling out to the test cluster
type kubectl struct {
type Kn struct {
namespace string
}

// Run the 'kubectl' CLI with args and opts
func (k kubectl) Run(args ...string) (string, error) {
return RunKubectl(k.namespace, args...)
// New Kn object
func NewKn() Kn {
maximilien marked this conversation as resolved.
Show resolved Hide resolved
return Kn{}
}

// Collector for results
type KnRunResultCollector struct {
results []KnRunResult
extraDumps []string
t *testing.T
}

func NewKnRunResultCollector(t *testing.T) *KnRunResultCollector {
return &KnRunResultCollector{
results: []KnRunResult{},
t: t,
extraDumps: []string{},
}
}

func (c *KnRunResultCollector) AssertNoError(result KnRunResult) {
c.results = append(c.results, result)
if result.Error != nil {
c.t.Logf("ERROR: %v", result.Stderr)
c.t.FailNow()
}
}

func (c *KnRunResultCollector) AssertError(result KnRunResult) {
c.results = append(c.results, result)
if result.Error == nil {
c.t.Log("ERROR: Error expected but no error happened")
c.t.FailNow()
}
// Run the 'kn' CLI with args and opts
func (k Kn) Run(args ...string) KnRunResult {
return RunKn(k.namespace, args)
}

// AddDump adds extra dump information to the collector which is printed
// out if an error occurs
func (c *KnRunResultCollector) AddDump(kind string, name string, namespace string) {
dumpInfo := extractDumpInfoWithName(kind, name, namespace)
if dumpInfo != "" {
c.extraDumps = append(c.extraDumps, dumpInfo)
}
// Namespace that this Kn instance uses
func (k Kn) Namespace() string {
return k.namespace
}

func (c *KnRunResultCollector) DumpIfFailed() {
if c.t.Failed() {
c.t.Log(c.errorDetails())
}
// Helper methods for calling out to the test cluster
type Kubectl struct {
namespace string
}

func (c *KnRunResultCollector) errorDetails() string {
var out = bytes.Buffer{}
fmt.Fprintln(&out, "=== FAIL: =======================[[ERROR]]========================")
c.printCommands(&out)
var dumpInfos []string
if len(c.results) > 0 {
dumpInfo := c.results[len(c.results)-1].DumpInfo
if dumpInfo != "" {
dumpInfos = append(dumpInfos, dumpInfo)
}
}
dumpInfos = append(dumpInfos, c.extraDumps...)
for _, d := range dumpInfos {
fmt.Fprintln(&out, "--------------------------[[DUMP]]-------------------------------")
fmt.Fprintf(&out, d)
// New Kn object
maximilien marked this conversation as resolved.
Show resolved Hide resolved
func NewKubectl(namespace string) Kubectl {
return Kubectl{
namespace: namespace,
}

fmt.Fprintln(&out, "=================================================================")
return out.String()
}

func (c *KnRunResultCollector) printCommands(out io.Writer) {
for i, result := range c.results {
c.printCommand(out, result)
if i < len(c.results)-1 {
fmt.Fprintf(out, "┣━%s\n", seperatorHeavy)
}
}
// Run the 'kubectl' CLI with args and opts
func (k Kubectl) Run(args ...string) (string, error) {
return RunKubectl(k.namespace, args...)
}

func (c *KnRunResultCollector) printCommand(out io.Writer, result KnRunResult) {
fmt.Fprintf(out, "🦆 %s\n", result.CmdLine)
for _, l := range strings.Split(result.Stdout, "\n") {
fmt.Fprintf(out, "┃ %s\n", l)
}
if result.Stderr != "" {
errorPrefix := "🔥"
if result.ErrorExpected {
errorPrefix = "︙"
}
for _, l := range strings.Split(result.Stderr, "\n") {
fmt.Fprintf(out, "%s %s\n", errorPrefix, l)
}
}
// Namespace that this Kn instance uses
maximilien marked this conversation as resolved.
Show resolved Hide resolved
func (k Kubectl) Namespace() string {
return k.namespace
}

// ========================================================
// Functions:

// Result of a "kn" call
type KnRunResult struct {
// Command line called
CmdLine string
// Standard output of command
Stdout string
// Standard error of command
Stderr string
// And extra dump informations in case of an unexpected error
DumpInfo string
// Error occurred during execution
Error error
// Was an error expected ?
ErrorExpected bool
}
// Public functions

// RunKn runs "kn" in a given namespace
func RunKn(namespace string, args []string) KnRunResult {
Expand Down Expand Up @@ -195,6 +106,8 @@ func RunKubectl(namespace string, args ...string) (string, error) {
return stdout, nil
}

// Private

func runCli(cli string, args []string) (string, string, error) {
var stderr bytes.Buffer
var stdout bytes.Buffer
Expand Down
7 changes: 4 additions & 3 deletions test/e2e/e2e_flags.go → lib/test/integration/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e
package integration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the package name could simply be e2e ?

imports like lib/test/e2e/flags

or even shorter lib/e2e/flags

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with e2e for short, other than integration. just feel too long.
If we don't have libraries for unit test, maybe just lib/test/flags.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use e2e it will conflict with the other one in test/e2e

Copy link
Collaborator

@navidshaikh navidshaikh Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use e2e it will conflict with the other one in test/e2e

How?

if we use test; the import path would look like (following Daisy's suggestion)

  • knative.dev/client/lib/test

while the one you mentioned would be

  • knative.dev/client/test/e2e

These are two different paths, I dont see any conflicts.
(even if we use e2e it would be lib/e2e/; still no conflicts as the parent path is different)

We need to get this right, as immediately after merge, there would be consumer of this import path.
Any subsequent change in this part of client repo would require changes in consumers as well.


import (
"flag"
Expand All @@ -23,14 +23,15 @@ import (

// Flags holds the command line flags or defaults for settings in the user's environment.
// See ClientFlags for the list of supported fields.
var Flags = initializeFlags()
var Flags = InitializeFlags()

// ClientFlags define the flags that are needed to run the e2e tests.
type ClientFlags struct {
DockerConfigJSON string
}

func initializeFlags() *ClientFlags {
// InitializeFlags initializes the client's flags
func InitializeFlags() *ClientFlags {
var f ClientFlags

dockerConfigJSON := os.Getenv("DOCKER_CONFIG_JSON")
Expand Down
65 changes: 38 additions & 27 deletions test/e2e/common.go → lib/test/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e
package integration

import (
"fmt"
Expand All @@ -38,60 +38,70 @@ var serviceMutex sync.Mutex
var serviceCount int
var namespaceCount int

type e2eTest struct {
// IntegrationTest struct
type Test struct {
maximilien marked this conversation as resolved.
Show resolved Hide resolved
namespace string
kn kn
kn Kn
}

func NewE2eTest() (*e2eTest, error) {
ns := nextNamespace()
// Teardown clean up
func (test *Test) Teardown() error {
return DeleteNamespace(test.namespace)
}

// Teardown clean up
func (test *Test) Kn() Kn {
return test.kn
}

err := createNamespace(ns)
// NewIntegrationTest creates a new ItegrationTest object
func NewIntegrationTest() (*Test, error) {
ns := NextNamespace()

err := CreateNamespace(ns)
if err != nil {
return nil, err
}
err = waitForNamespaceCreated(ns)
err = WaitForNamespaceCreated(ns)
if err != nil {
return nil, err
}

return &e2eTest{
return &Test{
namespace: ns,
kn: kn{ns},
kn: Kn{ns},
}, nil
}

func nextNamespace() string {
// NextNamespace return the next unique namespace
func NextNamespace() string {
ns := os.Getenv("KN_E2E_NAMESPACE")
if ns == "" {
ns = "kne2etests"
}
return fmt.Sprintf("%s%d", ns, getNextNamespaceId())
return fmt.Sprintf("%s%d", ns, GetNextNamespaceId())
}

func getNextNamespaceId() int {
// GetNextNamespaceId return the next unique ID for the next namespace
func GetNextNamespaceId() int {
nsMutex.Lock()
defer nsMutex.Unlock()
current := namespaceCount
namespaceCount++
return current
}

func getNextServiceName(base string) string {
// GetNextServiceName return the name for the next namespace
func GetNextServiceName(base string) string {
serviceMutex.Lock()
defer serviceMutex.Unlock()
current := serviceCount
serviceCount++
return base + strconv.Itoa(current)
}

// Teardown clean up
func (test *e2eTest) Teardown() error {
return deleteNamespace(test.namespace)
}

// createNamespace creates and tests a namesspace creation invoking kubectl
func createNamespace(namespace string) error {
// CreateNamespace creates and tests a namesspace creation invoking kubectl
func CreateNamespace(namespace string) error {
expectedOutputRegexp := fmt.Sprintf("namespace?.+%s.+created", namespace)
out, err := createNamespaceWithRetry(namespace, MaxRetries)
if err != nil {
Expand All @@ -109,9 +119,9 @@ func createNamespace(namespace string) error {
return nil
}

// createNamespace deletes and tests a namesspace deletion invoking kubectl
func deleteNamespace(namespace string) error {
kubectl := kubectl{namespace}
// DeleteNamespace deletes and tests a namesspace deletion invoking kubectl
func DeleteNamespace(namespace string) error {
kubectl := Kubectl{namespace}
out, err := kubectl.Run("delete", "namespace", namespace)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Cannot delete namespace %s", namespace))
Expand All @@ -129,7 +139,7 @@ func deleteNamespace(namespace string) error {
}

// WaitForNamespaceDeleted wait until namespace is deleted
func waitForNamespaceDeleted(namespace string) error {
func WaitForNamespaceDeleted(namespace string) error {
deleted := checkNamespace(namespace, false, MaxRetries)
if !deleted {
return fmt.Errorf("error deleting namespace %s, timed out after %d retries", namespace, MaxRetries)
Expand All @@ -138,7 +148,7 @@ func waitForNamespaceDeleted(namespace string) error {
}

// WaitForNamespaceCreated wait until namespace is created
func waitForNamespaceCreated(namespace string) error {
func WaitForNamespaceCreated(namespace string) error {
created := checkNamespace(namespace, true, MaxRetries)
if !created {
return fmt.Errorf("error creating namespace %s, timed out after %d retries", namespace, MaxRetries)
Expand All @@ -147,10 +157,11 @@ func waitForNamespaceCreated(namespace string) error {
}

// Private functions

func checkNamespace(namespace string, created bool, maxRetries int) bool {
retries := 0
for retries < MaxRetries {
output, _ := kubectl{}.Run("get", "namespace")
output, _ := Kubectl{}.Run("get", "namespace")

// check for namespace deleted
if !created && !strings.Contains(output, namespace) {
Expand All @@ -177,7 +188,7 @@ func createNamespaceWithRetry(namespace string, maxRetries int) (string, error)
)

for retries < maxRetries {
out, err = kubectl{}.Run("create", "namespace", namespace)
out, err = Kubectl{}.Run("create", "namespace", namespace)
if err == nil {
return out, nil
}
Expand Down
Loading