2021-04-16 22:33:50 +00:00
/ *
Copyright 2021 The Kubernetes Authors All rights reserved .
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 generate
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
2021-10-11 01:46:44 +00:00
"log"
2021-04-16 22:33:50 +00:00
"os"
"path/filepath"
2021-10-11 01:46:44 +00:00
"regexp"
2021-04-16 22:33:50 +00:00
"strings"
"time"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/out"
)
2021-12-05 09:26:25 +00:00
// TestDocs generates list of tests
2021-05-11 22:17:43 +00:00
func TestDocs ( docPath string , pathToCheck string ) error {
2021-04-16 22:33:50 +00:00
buf := bytes . NewBuffer ( [ ] byte { } )
date := time . Now ( ) . Format ( "2006-01-02" )
2021-11-18 18:55:32 +00:00
title := out . Fmt ( title , out . V { "Command" : "List of Integration Test Cases" , "Description" : "Auto generated list of all minikube integration tests and what they do." , "Date" : date } )
2021-04-16 22:33:50 +00:00
_ , err := buf . Write ( [ ] byte ( title ) )
if err != nil {
return err
}
2021-05-11 22:17:43 +00:00
err = filepath . Walk ( pathToCheck , func ( path string , info os . FileInfo , err error ) error {
2021-04-16 22:33:50 +00:00
if info . IsDir ( ) || ! strings . HasSuffix ( path , ".go" ) {
return nil
}
fset := token . NewFileSet ( )
2021-09-28 10:58:01 +00:00
r , e := os . ReadFile ( path )
2021-04-16 22:33:50 +00:00
if e != nil {
return errors . Wrap ( e , fmt . Sprintf ( "error reading file %s" , path ) )
}
file , e := parser . ParseFile ( fset , "" , r , parser . ParseComments )
if e != nil {
return errors . Wrap ( e , fmt . Sprintf ( "error parsing file %s" , path ) )
}
ast . Inspect ( file , func ( x ast . Node ) bool {
if fd , ok := x . ( * ast . FuncDecl ) ; ok {
fnName := fd . Name . Name
if ! shouldParse ( fnName ) {
return true
}
2021-10-11 01:46:44 +00:00
td := parseFuncDocs ( file , fd )
_ , e := buf . WriteString ( td . toMarkdown ( ) )
2021-04-16 22:45:44 +00:00
if e != nil {
return false
2021-04-16 22:33:50 +00:00
}
}
return true
} )
return nil
} )
if err != nil {
return err
}
2021-09-28 10:58:01 +00:00
err = os . WriteFile ( docPath , buf . Bytes ( ) , 0 o644 )
2021-04-16 22:33:50 +00:00
return err
}
2021-10-11 01:46:44 +00:00
// TestDoc is the documentation for a test case
type TestDoc struct {
// name is the name of the test case
name string
// isSubTest is true if the test case is a top-level test case, false if it's a validation method
isSubTest bool
// description is parsed from the function comment
description string
// steps are parsed from comments starting with `docs: `
steps [ ] string
// specialCases are parsed from comments starting with `docs(special): `
specialCases [ ] string
// specialCases are parsed from comments starting with `docs(skip): `
skips [ ] string
}
// toMarkdown converts the TestDoc into a string in Markdown format
func ( d * TestDoc ) toMarkdown ( ) string {
b := & strings . Builder { }
if d . isSubTest {
b . WriteString ( "#### " + d . name + "\n" )
} else {
b . WriteString ( "## " + d . name + "\n" )
}
b . WriteString ( d . description + "\n" )
if len ( d . steps ) > 0 {
b . WriteString ( "Steps:\n" )
for _ , s := range d . steps {
b . WriteString ( "- " + s + "\n" )
}
b . WriteString ( "\n" )
}
if len ( d . specialCases ) > 0 {
b . WriteString ( "Special cases:\n" )
for _ , s := range d . specialCases {
b . WriteString ( "- " + s + "\n" )
}
b . WriteString ( "\n" )
}
if len ( d . skips ) > 0 {
b . WriteString ( "Skips:\n" )
for _ , s := range d . skips {
b . WriteString ( "- " + s + "\n" )
}
b . WriteString ( "\n" )
}
return b . String ( )
}
// docsRegex is the regex of the docs comment starting with either `docs: ` or `docs(...): `
var docsRegex = regexp . MustCompile ( ` docs(?:\((.*?)\))?:\s* ` )
// parseFuncDocs parses the comments from a function starting with `docs`
func parseFuncDocs ( file * ast . File , fd * ast . FuncDecl ) TestDoc {
d := TestDoc {
name : fd . Name . Name ,
description : strings . TrimPrefix ( fd . Doc . Text ( ) , fd . Name . Name + " " ) ,
isSubTest : strings . HasPrefix ( fd . Name . Name , "valid" ) ,
}
for _ , c := range file . Comments {
for _ , ci := range c . List {
if ci . Pos ( ) < fd . Pos ( ) || ci . End ( ) > fd . End ( ) {
// only generate docs for comments that are within the function scope
continue
}
text := strings . TrimPrefix ( ci . Text , "// " )
m := docsRegex . FindStringSubmatch ( text )
if len ( m ) < 2 {
// comment doesn't start with `docs: ` or `docs(...): `
continue
}
matched := m [ 0 ]
docsType := m [ 1 ]
text = strings . TrimPrefix ( text , matched )
switch docsType {
case "special" :
d . specialCases = append ( d . specialCases , text )
case "skip" :
d . skips = append ( d . skips , text )
case "" :
d . steps = append ( d . steps , text )
default :
log . Printf ( "docs type %s is not recognized" , docsType )
}
}
}
return d
}
2021-04-16 22:33:50 +00:00
func shouldParse ( name string ) bool {
if strings . HasPrefix ( name , "Test" ) && ! strings . HasPrefix ( name , "TestMain" ) {
return true
}
if strings . HasPrefix ( name , "valid" ) {
return true
}
return false
}