Documentation
¶
Index ¶
- func IsValidTestCase(funcDecl *ast.FuncDecl) (valid bool, badFormat bool)
- type AnalysisResult
- func (ar *AnalysisResult) AttemptRefactoring(strategy RefactorStrategy, keepRefactoredFiles bool) RefactorResult
- func (ar *AnalysisResult) EncodeAsCSV() []string
- func (ar *AnalysisResult) GetCSVHeaders() []string
- func (ar *AnalysisResult) IsTableDriven() bool
- func (ar *AnalysisResult) SaveAsJSON(dir string) error
- type ExpandedStatement
- type ExpressionDefinition
- type RefactorGenerationStatus
- type RefactorResult
- type RefactorStrategy
- type RefactoredFunction
- type Scenario
- type ScenarioDataStructure
- type ScenarioSet
- func (ss *ScenarioSet) Analyze()
- func (ss *ScenarioSet) GetFields() iter.Seq[*types.Var]
- func (ss *ScenarioSet) GetRunnerStatements() []ast.Stmt
- func (ss *ScenarioSet) IdentifyScenarios(expr ast.Expr, tc *TestCase) bool
- func (ss *ScenarioSet) IsTableDriven() bool
- func (ss *ScenarioSet) MarshalJSON() ([]byte, error)
- type TestCase
- func (tc *TestCase) Execute() (TestExecutionResult, error)
- func (tc *TestCase) FileSet() *token.FileSet
- func (tc *TestCase) GetFile() *ast.File
- func (tc *TestCase) GetFuncDecl() *ast.FuncDecl
- func (tc *TestCase) GetImportPath() string
- func (tc *TestCase) GetImportPathRoot() string
- func (tc *TestCase) GetJSONFilePath(dir string) string
- func (tc *TestCase) GetPackageFiles() []*ast.File
- func (tc *TestCase) GetPackageInfo() *packages.Package
- func (tc *TestCase) GetStatements() []ast.Stmt
- func (tc *TestCase) MarshalJSON() ([]byte, error)
- func (tc *TestCase) NumLines() int
- func (tc *TestCase) NumStatements() int
- func (tc *TestCase) ObjectOf(ident *ast.Ident) types.Object
- func (tc *TestCase) String() string
- func (tc *TestCase) TypeInfo() *types.Info
- func (tc *TestCase) TypeOf(expr ast.Expr) types.Type
- func (tc *TestCase) UnmarshalJSON(data []byte) error
- type TestExecutionResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsValidTestCase ¶
Determine if the given function declaration is a valid test case. Returns two booleans: `valid` indicating whether this is a valid test case, and `badFormat` indicating whether the test case has an incorrect (but acceptable) format. `badFormat` is false if the function is not valid.
The test case is validated using the following criteria: - The function name starts with "Test" followed by a capital letter - The function has `*testing.T` as its only formal parameter - The function does not have any receiver (i.e., it is not a method) - The function does not have any generic type parameters - The function does not return any values
Types ¶
type AnalysisResult ¶ added in v1.2.0
type AnalysisResult struct {
// Reference to the original test case being analyzed
TestCase *TestCase
// Analysis data
ScenarioSet *ScenarioSet // the set of scenarios defined in this test case, if it is table-driven
ParsedStatements []*ExpandedStatement // the list of parsed and fully-expanded statements in the test case
ImportedPackages []string // the list of imported packages in the test case's file
// Refactoring result - only available after running `AttemptRefactoring()`
RefactorResult RefactorResult // the result of refactoring the test case
}
Represents the result of analyzing a TestCase, including information about its table-driven structure.
func Analyze ¶ added in v1.2.0
func Analyze(tc *TestCase) *AnalysisResult
Extracts relevant information about a TestCase and saves the results to a new AnalysisResult instance
func (*AnalysisResult) AttemptRefactoring ¶ added in v1.2.0
func (ar *AnalysisResult) AttemptRefactoring(strategy RefactorStrategy, keepRefactoredFiles bool) RefactorResult
Attempts to refactor a test case using the specified strategy. If a refactoring is successfully generated, the test is executed using the original and refactored code. The default behavior is to restore the original file contents after the refactoring is complete, but this can be disabled by setting `keepRefactoredFiles` to true. Saves the result of the refactoring attempt to the AnalysisResult, and also returns a copy of the result.
func (*AnalysisResult) EncodeAsCSV ¶ added in v1.2.0
func (ar *AnalysisResult) EncodeAsCSV() []string
Encode the AnalysisResult as a CSV row, returning the encoded data corresponding to the headers in `GetCSVHeaders()`.
func (*AnalysisResult) GetCSVHeaders ¶ added in v1.2.0
func (ar *AnalysisResult) GetCSVHeaders() []string
Return the headers for the CSV representation of the AnalysisResult. Complex or large fields are excluded for the sake of brevity.
func (*AnalysisResult) IsTableDriven ¶ added in v1.2.0
func (ar *AnalysisResult) IsTableDriven() bool
Return whether the test case is table-driven, based on the detected ScenarioSet data
func (*AnalysisResult) SaveAsJSON ¶ added in v1.2.0
func (ar *AnalysisResult) SaveAsJSON(dir string) error
Save the AnalysisResult as JSON to a file named like `<project>/<project>_<package>_<testName>.json` in the specified directory (or the output directory if not specified).
type ExpandedStatement ¶ added in v1.2.0
type ExpandedStatement struct {
// The original statement
Stmt ast.Stmt
// The expanded form of the called function's inner statements, or nil if the statement is not a function call
Children []*ExpandedStatement
// contains filtered or unexported fields
}
Represents the expanded form of a function call statement as a G-tree. If the statement is a function call, its inner statements are expanded recursively and stored in `Children`. If the statement involves function calls somehow (e.g. as part of an assignment or conditional statement), those calls are also considered as children. If the statement is not a function call and does not involve any function calls, its `Children` field is nil.
func ExpandStatement ¶ added in v1.2.0
func ExpandStatement(stmt ast.Stmt, tc *TestCase, testOnly bool) *ExpandedStatement
Recursively create the fully expanded form of a function call statement, expanding depth first. If `testOnly` is true, only expand statements that are defined in a file with a `_test.go` suffix. Note that functions are only expanded when they're called, so function literals (e.g. inside `t.Run()`) are not expanded.
func (*ExpandedStatement) All ¶ added in v1.2.0
func (es *ExpandedStatement) All() iter.Seq[ast.Stmt]
Returns an iterator over all the statements contained within the ExpandedStatement
func (*ExpandedStatement) MarshalJSON ¶ added in v1.2.0
func (es *ExpandedStatement) MarshalJSON() ([]byte, error)
Marshal a TestCase for JSON output
func (*ExpandedStatement) String ¶ added in v1.2.0
func (es *ExpandedStatement) String() string
Return a string representation of an expanded statement, including the stringified versions of its children.
func (*ExpandedStatement) UnmarshalJSON ¶ added in v1.2.0
func (es *ExpandedStatement) UnmarshalJSON(data []byte) error
Unmarshal a TestCase from JSON
type ExpressionDefinition ¶ added in v1.2.0
type ExpressionDefinition struct {
// The AST node representing the actual expression definition
Node ast.Node
// The AST file that contains the definition, or nil if it was not found
File *ast.File
}
Represents the definition of an expression as found by FindDefinition.
func FindDefinition ¶ added in v1.2.0
Return the AST definition and of the expression within the specified TestCase's package, if it exists. Also returns the AST file that contains the definition if it is successfully found, or nil in all other cases. If the expression is not an identifier or selector expression, returns the original expression. Returns nil for both return values (indicating that the definition was deliberately excluded) in the following cases:
- The expression is not defined in the specified context package
- If `testOnly` is true and the expression is not defined in a file with a `_test.go` suffix
type RefactorGenerationStatus ¶ added in v1.2.0
type RefactorGenerationStatus int
Represents the status of an attempt to generate refactored code for a test case.
const ( RefactorGenerationStatusNone RefactorGenerationStatus = iota // No refactoring was attempted RefactorGenerationStatusError // Refactoring could not be performed properly due to an unrecoverable error, e.g. due to a logic error RefactorGenerationStatusBadFields // Refactoring failed based on the configuration of the scenario fields RefactorGenerationStatusNoTester // Refactoring failed because a `*testing.T` variable could not be detected RefactorGenerationStatusFail // Refactoring failed unexpectedly, e.g. due to an unusual AST structure RefactorGenerationStatusSuccess // Refactoring was successful )
func (RefactorGenerationStatus) MarshalJSON ¶ added in v1.2.0
func (rs RefactorGenerationStatus) MarshalJSON() ([]byte, error)
func (RefactorGenerationStatus) String ¶ added in v1.2.0
func (rs RefactorGenerationStatus) String() string
func (*RefactorGenerationStatus) UnmarshalJSON ¶ added in v1.2.0
func (rs *RefactorGenerationStatus) UnmarshalJSON(data []byte) error
type RefactorResult ¶ added in v1.2.0
type RefactorResult struct {
// The refactoring strategy that was applied, if any
Strategy RefactorStrategy `json:"strategy"`
// The status of the refactor generation attempt
GenerationStatus RefactorGenerationStatus `json:"status"`
// The contents of the refactored test case, if the refactor generation was successful
Refactorings []RefactoredFunction `json:"refactorings"`
// The results of executing the test case before and after refactoring
OriginalExecutionResult TestExecutionResult `json:"originalTestResult"`
RefactoredExecutionResult TestExecutionResult `json:"refactoredTestResult"`
}
Represents the result of a refactoring attempt on a test case.
type RefactorStrategy ¶ added in v1.2.0
type RefactorStrategy int
Represents a refactoring strategy that can be applied to a test case. Each value corresponds to a refactoring method with a similar name.
const ( RefactorStrategyNone RefactorStrategy = iota // No refactoring method specified RefactorStrategySubtest // Wrap the entire contents of the execution loop in a call to `t.Run()` )
func RefactorStrategyFromString ¶ added in v1.2.0
func RefactorStrategyFromString(method string) RefactorStrategy
Return the RefactorStrategy corresponding to the given string.
func (RefactorStrategy) MarshalJSON ¶ added in v1.2.0
func (rm RefactorStrategy) MarshalJSON() ([]byte, error)
func (RefactorStrategy) String ¶ added in v1.2.0
func (rm RefactorStrategy) String() string
func (*RefactorStrategy) UnmarshalJSON ¶ added in v1.2.0
func (rm *RefactorStrategy) UnmarshalJSON(data []byte) error
type RefactoredFunction ¶ added in v1.2.0
type RefactoredFunction struct {
Refactored *ast.FuncDecl `json:"-"` // The actual refactored function declaration
RefactoredString string `json:"refactored"` // The string representation of the refactored function declaration
File *ast.File `json:"-"` // The AST file where the refactored function is defined
FilePath string `json:"filePath"` // The path to the file containing the refactored function
// contains filtered or unexported fields
}
Represents function declaration that has been refactored. Note that the AST file is not guaranteed to retain the modified function data after Cleanup is called, but it should still be retained by the FuncDecl reference (and by the string forms of these AST elements).
func NewRefactoredFunction ¶ added in v1.2.0
func NewRefactoredFunction(fn *ast.FuncDecl, file *ast.File, cleanupFunc func() error, fset *token.FileSet) *RefactoredFunction
Creates a new RefactoredFunction with the provided AST data.
func (*RefactoredFunction) Cleanup ¶ added in v1.2.0
func (rf *RefactoredFunction) Cleanup()
Cleans up the refactored function by restoring the original function declaration, if possible.
func (*RefactoredFunction) UpdateStringRepresentation ¶ added in v1.2.0
func (rf *RefactoredFunction) UpdateStringRepresentation(fset *token.FileSet)
Performs an in-place update of the string representation of the refactored AST function declaration already stored in the RefactoredFunction, using the provided FileSet.
type Scenario ¶ added in v1.1.1
type Scenario struct {
Name string // the detected name of the scenario, either from a "name" or "desc" field, or the key of a map
Expr ast.Expr
}
Represents an individual scenario defined by a table-driven test todo LATER not implemented yet
type ScenarioDataStructure ¶ added in v1.1.1
type ScenarioDataStructure int
Represents the type of data structure used to store scenarios
const ( ScenarioNoDS ScenarioDataStructure = iota // no table-driven test structure detected ScenarioStructListDS // table-driven test using a slice or array of structs ScenarioMapDS // table-driven test using a map )
func (ScenarioDataStructure) MarshalJSON ¶ added in v1.1.1
func (sds ScenarioDataStructure) MarshalJSON() ([]byte, error)
func (ScenarioDataStructure) String ¶ added in v1.1.1
func (sds ScenarioDataStructure) String() string
func (*ScenarioDataStructure) UnmarshalJSON ¶ added in v1.1.1
func (sds *ScenarioDataStructure) UnmarshalJSON(data []byte) error
type ScenarioSet ¶ added in v1.1.1
type ScenarioSet struct {
// Reference to the TestCase this ScenarioSet belongs to
TestCase *TestCase
// Core data fields
// todo LATER expand to support scenario definitions like `map[string]bool` without a struct template (probably by making changes to `DetectScenarioDataStructure`)
ScenarioTemplate *types.Struct // the definition of the `struct` type that individual scenarios are based on
DataStructure ScenarioDataStructure // describes the type of data structure used to store scenarios
Scenarios []ast.Expr // the individual scenarios themselves //todo LATER convert to type `[]Scenario`
Runner ast.Stmt // the actual code that runs the subtest (which is expected to be either a `ForStmt` or a `RangeStmt`)
// Derived analysis results
NameField string // the name of the field representing each scenario's name, or "map key" if the map key is used as the name
ExpectedFields []string // the names of fields representing the expected results of each scenario
HasFunctionFields bool // whether the scenario type has any fields whose type is a function
UsesSubtest bool // whether the test calls `t.Run()` inside the loop body
}
Represents the properties of a table-driven test by storing information about the scenarios and their structure, as well as various analysis results derived from this information.
func IdentifyScenarioSet ¶ added in v1.2.0
func IdentifyScenarioSet(tc *TestCase, statements []*ExpandedStatement) *ScenarioSet
Attempts to extract the table-driven properties of a test case using information extracted from its parsed statements
func (*ScenarioSet) Analyze ¶ added in v1.1.1
func (ss *ScenarioSet) Analyze()
Perform additional analysis based on the core data fields, populating the corresponding fields
func (*ScenarioSet) GetFields ¶ added in v1.1.1
func (ss *ScenarioSet) GetFields() iter.Seq[*types.Var]
Returns the fields of the scenario struct definition todo note that defining fields like `a, b int` counts as one `Field` element with multiple Names -- need to account for this
func (*ScenarioSet) GetRunnerStatements ¶ added in v1.2.0
func (ss *ScenarioSet) GetRunnerStatements() []ast.Stmt
Returns the statements that make up the loop body
func (*ScenarioSet) IdentifyScenarios ¶ added in v1.2.0
func (ss *ScenarioSet) IdentifyScenarios(expr ast.Expr, tc *TestCase) bool
Checks whether an expression has the same underlying type as the ScenarioTemplate, and if so, saves the scenarios from the expression. Returns whether the scenarios were saved successfully. Always returns `false` if the `ScenarioSet.DataStructure` is unknown. See https://go.dev/ref/spec#Type_identity for details of the `types.Identical` comparison method.
func (*ScenarioSet) IsTableDriven ¶ added in v1.2.0
func (ss *ScenarioSet) IsTableDriven() bool
Returns whether the detected information in the ScenarioSet is indicative of a table-driven test
func (*ScenarioSet) MarshalJSON ¶ added in v1.1.1
func (ss *ScenarioSet) MarshalJSON() ([]byte, error)
Marshal the ScenarioSet for JSON output
type TestCase ¶
type TestCase struct {
// High-level identifiers
TestName string // the name of the test case itself
PackageName string // the name of the package where the test case is defined, as it appears in the source code
FilePath string // the path to the file where the test case is defined
ProjectName string // the name of the overarching project that the test case is part of
// contains filtered or unexported fields
}
Represents an individual test case defined at the top level of a Go source file.
func CreateTestCase ¶
func CreateTestCase(funcDecl *ast.FuncDecl, file *ast.File, pkg *packages.Package, project string) TestCase
Create a new TestCase struct for storage and analysis todo return error value more clearly either by returning nil or an error type
func (*TestCase) Execute ¶ added in v1.2.0
func (tc *TestCase) Execute() (TestExecutionResult, error)
Execute a test based on the contents of its corresponding file in the file system using `go test`, and return the results. Returns an error if the test fails for any reason.
func (*TestCase) FileSet ¶ added in v1.2.0
Get the the FileSet used for parsing the test's entire project
func (*TestCase) GetFuncDecl ¶ added in v1.2.0
Get the AST function declaration for the test case
func (*TestCase) GetImportPath ¶ added in v1.2.0
Get the entire import path of the test case's package
func (*TestCase) GetImportPathRoot ¶ added in v1.2.0
Get the "repository root path" part of the test case's package import path. This is the part of the import path before the third slash, e.g. "github.com/user/repo"
func (*TestCase) GetJSONFilePath ¶ added in v1.2.0
Return the filepath where the test case's JSON representation should be saved, using the specified directory as a base if provided. The returned path is formatted like `<project>/<project>_<package>_<testName>.json`.
func (*TestCase) GetPackageFiles ¶ added in v1.2.0
Get all the AST files involved in the test case's package
func (*TestCase) GetPackageInfo ¶ added in v1.2.0
Get the container for all raw information about the test case's package
func (*TestCase) GetStatements ¶
Return the list of statements in this test case
func (*TestCase) MarshalJSON ¶
Marshal a TestCase for JSON output
func (*TestCase) NumLines ¶
Return the number of individual lines (not statements) that the test case spans, or 0 if the number of lines cannot be determined.
func (*TestCase) NumStatements ¶
Return the number of statements in the test case
func (*TestCase) ObjectOf ¶ added in v1.2.0
Convenience method for getting the Object corresponding to an identifier within the current TestCase's project. Returns `nil` if the type information for the project is not available, or if the identifier is not found.
func (*TestCase) String ¶
Return a string representation of the TestCase for logging and debugging purposes
func (*TestCase) TypeOf ¶ added in v1.1.1
Convenience method for getting the type of an expression (including identifiers) within the current TestCase's project. Returns `nil` if the type information for the project is not available, or if the expression is not found.
func (*TestCase) UnmarshalJSON ¶
Unmarshal a TestCase from JSON
type TestExecutionResult ¶ added in v1.2.0
type TestExecutionResult int
Represents the result of a test execution, as run by the `go test` command.
const ( TestExecutionResultNotRun TestExecutionResult = iota // The test was not executed TestExecutionResultCompilationError // The test failed to compile TestExecutionResultSkip // The test was skipped TestExecutionResultFail // The test failed TestExecutionResultPass // The test passed successfully )
func (TestExecutionResult) MarshalJSON ¶ added in v1.2.0
func (ter TestExecutionResult) MarshalJSON() ([]byte, error)
func (TestExecutionResult) String ¶ added in v1.2.0
func (ter TestExecutionResult) String() string
func (*TestExecutionResult) UnmarshalJSON ¶ added in v1.2.0
func (ter *TestExecutionResult) UnmarshalJSON(data []byte) error