mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
* chore(lint): added nlreturn, run linting * chore(lint): replace some fmt.Sprintf calls * chore(lint): replace fmt.Sprintf with strconv * chore(lint): add goconst, use http lib for status codes, and methods * chore(lint): use errors lib, errCodes from internal/errors * chore(lint): dupl, thelper and usetesting * chore(lint): fmt.Errorf %v to %w, more linters * chore(lint): paralleltest, where possible * perf(test): optimize test performance by 78% - Implement shared binary building with package-level cache to eliminate redundant builds - Add strategic parallelization to 15+ tests while preserving environment variable isolation - Implement thread-safe fixture caching with RWMutex to reduce I/O operations - Remove unnecessary working directory changes by leveraging embedded templates - Add embedded template system with go:embed directive for reliable template resolution - Fix linting issues: rename sharedBinaryError to errSharedBinary, add nolint directive Performance improvements: - Total test execution time: 12+ seconds → 2.7 seconds (78% faster) - Binary build overhead: 14+ separate builds → 1 shared build (93% reduction) - Parallel execution: Limited → 15+ concurrent tests (60-70% better CPU usage) - I/O operations: 66+ fixture reads → cached with sync.RWMutex (50% reduction) All tests maintain 100% success rate and coverage while running nearly 4x faster.
341 lines
9.3 KiB
Go
341 lines
9.3 KiB
Go
package internal
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/ivuorinen/gh-action-readme/testutil"
|
|
)
|
|
|
|
// TestGenerator_ComprehensiveGeneration demonstrates the new table-driven testing framework
|
|
// by testing generation across all fixtures, themes, and formats systematically.
|
|
func TestGenerator_ComprehensiveGeneration(t *testing.T) {
|
|
t.Parallel()
|
|
// Create test cases using the new helper functions
|
|
cases := testutil.CreateGeneratorTestCases()
|
|
|
|
// Filter to a subset for demonstration (full test would be very large)
|
|
filteredCases := make([]testutil.GeneratorTestCase, 0)
|
|
for _, testCase := range cases {
|
|
// Only test a few combinations for demonstration
|
|
if (testCase.Theme == "default" && testCase.OutputFormat == "md") ||
|
|
(testCase.Theme == "github" && testCase.OutputFormat == "html") ||
|
|
(testCase.Theme == "minimal" && testCase.OutputFormat == "json") {
|
|
// Add custom executor for generator tests
|
|
testCase.Executor = createGeneratorTestExecutor()
|
|
filteredCases = append(filteredCases, testCase)
|
|
}
|
|
}
|
|
|
|
// Run the test suite
|
|
testutil.RunGeneratorTests(t, filteredCases)
|
|
}
|
|
|
|
// TestGenerator_AllValidFixtures tests generation with all valid fixtures.
|
|
func TestGenerator_AllValidFixtures(t *testing.T) {
|
|
t.Parallel()
|
|
validFixtures := testutil.GetValidFixtures()
|
|
|
|
for _, fixture := range validFixtures {
|
|
fixture := fixture // capture loop variable
|
|
t.Run(fixture, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create temporary action from fixture
|
|
actionPath := testutil.CreateTemporaryAction(t, fixture)
|
|
|
|
// Test with default configuration
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
|
|
// Generate documentation
|
|
err := generator.GenerateFromFile(actionPath)
|
|
if err != nil {
|
|
t.Errorf("failed to generate documentation for fixture %s: %v", fixture, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGenerator_AllInvalidFixtures tests that invalid fixtures produce expected errors.
|
|
func TestGenerator_AllInvalidFixtures(t *testing.T) {
|
|
t.Parallel()
|
|
invalidFixtures := testutil.GetInvalidFixtures()
|
|
|
|
for _, fixture := range invalidFixtures {
|
|
fixture := fixture // capture loop variable
|
|
t.Run(fixture, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Some invalid fixtures might not be loadable
|
|
actionFixture, err := testutil.LoadActionFixture(fixture)
|
|
if err != nil {
|
|
// This is expected for some invalid fixtures
|
|
return
|
|
}
|
|
|
|
// Create temporary action from fixture
|
|
tempDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
testutil.WriteTestFile(t, tempDir+"/action.yml", actionFixture.Content)
|
|
|
|
// Test with default configuration
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
|
|
// Generate documentation - should fail
|
|
err = generator.GenerateFromFile(tempDir + "/action.yml")
|
|
if err == nil {
|
|
t.Errorf("expected generation to fail for invalid fixture %s, but it succeeded", fixture)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGenerator_AllThemes demonstrates theme testing using helper functions.
|
|
func TestGenerator_AllThemes(t *testing.T) {
|
|
t.Parallel()
|
|
// Use the helper function to test all themes
|
|
testutil.TestAllThemes(t, func(t *testing.T, theme string) {
|
|
t.Helper()
|
|
// Create a simple action for testing
|
|
actionPath := testutil.CreateTemporaryAction(t, "actions/javascript/simple.yml")
|
|
|
|
config := &AppConfig{
|
|
Theme: theme,
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
testutil.AssertNoError(t, err)
|
|
})
|
|
}
|
|
|
|
// TestGenerator_AllFormats demonstrates format testing using helper functions.
|
|
func TestGenerator_AllFormats(t *testing.T) {
|
|
t.Parallel()
|
|
// Use the helper function to test all formats
|
|
testutil.TestAllFormats(t, func(t *testing.T, format string) {
|
|
t.Helper()
|
|
// Create a simple action for testing
|
|
actionPath := testutil.CreateTemporaryAction(t, "actions/javascript/simple.yml")
|
|
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: format,
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
testutil.AssertNoError(t, err)
|
|
})
|
|
}
|
|
|
|
// TestGenerator_ByActionType demonstrates testing by action type.
|
|
func TestGenerator_ByActionType(t *testing.T) {
|
|
t.Parallel()
|
|
actionTypes := []testutil.ActionType{
|
|
testutil.ActionTypeJavaScript,
|
|
testutil.ActionTypeComposite,
|
|
testutil.ActionTypeDocker,
|
|
}
|
|
|
|
for _, actionType := range actionTypes {
|
|
actionType := actionType // capture loop variable
|
|
t.Run(string(actionType), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fixtures := testutil.GetFixturesByActionType(actionType)
|
|
if len(fixtures) == 0 {
|
|
t.Skipf("no fixtures available for action type %s", actionType)
|
|
}
|
|
|
|
// Test the first fixture of this type
|
|
fixture := fixtures[0]
|
|
actionPath := testutil.CreateTemporaryAction(t, fixture)
|
|
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
testutil.AssertNoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGenerator_WithMockEnvironment demonstrates testing with a complete mock environment.
|
|
func TestGenerator_WithMockEnvironment(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a complete test environment
|
|
envConfig := &testutil.EnvironmentConfig{
|
|
ActionFixtures: []string{"actions/composite/with-dependencies.yml"},
|
|
WithMocks: true,
|
|
}
|
|
|
|
env := testutil.CreateTestEnvironment(t, envConfig)
|
|
|
|
// Clean up environment
|
|
defer func() {
|
|
for _, cleanup := range env.Cleanup {
|
|
if err := cleanup(); err != nil {
|
|
t.Errorf("cleanup failed: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
if len(env.ActionPaths) == 0 {
|
|
t.Fatal("expected at least one action path")
|
|
}
|
|
|
|
config := &AppConfig{
|
|
Theme: "github",
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Quiet: true,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
err := generator.GenerateFromFile(env.ActionPaths[0])
|
|
|
|
testutil.AssertNoError(t, err)
|
|
}
|
|
|
|
// TestGenerator_FixtureValidation demonstrates fixture validation.
|
|
func TestGenerator_FixtureValidation(t *testing.T) {
|
|
t.Parallel()
|
|
// Test that all valid fixtures pass validation
|
|
validFixtures := testutil.GetValidFixtures()
|
|
|
|
for _, fixtureName := range validFixtures {
|
|
t.Run(fixtureName, func(t *testing.T) {
|
|
testutil.AssertFixtureValid(t, fixtureName)
|
|
})
|
|
}
|
|
|
|
// Test that all invalid fixtures fail validation
|
|
invalidFixtures := testutil.GetInvalidFixtures()
|
|
|
|
for _, fixtureName := range invalidFixtures {
|
|
t.Run(fixtureName, func(t *testing.T) {
|
|
t.Parallel()
|
|
testutil.AssertFixtureInvalid(t, fixtureName)
|
|
})
|
|
}
|
|
}
|
|
|
|
// createGeneratorTestExecutor returns a test executor function for generator tests.
|
|
func createGeneratorTestExecutor() testutil.TestExecutor {
|
|
return func(t *testing.T, testCase testutil.TestCase, ctx *testutil.TestContext) *testutil.TestResult {
|
|
t.Helper()
|
|
|
|
result := &testutil.TestResult{
|
|
Context: ctx,
|
|
}
|
|
|
|
var actionPath string
|
|
|
|
// If we have a fixture, load it and create action file
|
|
if testCase.Fixture != "" {
|
|
fixture, err := ctx.FixtureManager.LoadActionFixture(testCase.Fixture)
|
|
if err != nil {
|
|
result.Error = fmt.Errorf("failed to load fixture %s: %w", testCase.Fixture, err)
|
|
|
|
return result
|
|
}
|
|
|
|
// Create temporary action file
|
|
actionPath = filepath.Join(ctx.TempDir, "action.yml")
|
|
testutil.WriteTestFile(t, actionPath, fixture.Content)
|
|
}
|
|
|
|
// If we don't have an action file to test, just return success
|
|
if actionPath == "" {
|
|
result.Success = true
|
|
|
|
return result
|
|
}
|
|
|
|
// Create generator configuration from test config
|
|
config := createGeneratorConfigFromTestConfig(ctx.Config, ctx.TempDir)
|
|
|
|
// Debug: Log the template path (no working directory changes needed with embedded templates)
|
|
t.Logf("Using template path: %s", config.Template)
|
|
|
|
// Create and run generator
|
|
generator := NewGenerator(config)
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
if err != nil {
|
|
result.Error = err
|
|
result.Success = false
|
|
} else {
|
|
result.Success = true
|
|
// Detect generated files
|
|
result.Files = testutil.DetectGeneratedFiles(ctx.TempDir, config.OutputFormat)
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
// createGeneratorConfigFromTestConfig converts TestConfig to AppConfig.
|
|
func createGeneratorConfigFromTestConfig(testConfig *testutil.TestConfig, outputDir string) *AppConfig {
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: "md",
|
|
OutputDir: outputDir,
|
|
Template: "templates/readme.tmpl",
|
|
Schema: "schemas/schema.json",
|
|
Verbose: false,
|
|
Quiet: true, // Default to quiet for tests
|
|
GitHubToken: "",
|
|
}
|
|
|
|
// Override with test-specific settings
|
|
if testConfig != nil {
|
|
if testConfig.Theme != "" {
|
|
config.Theme = testConfig.Theme
|
|
}
|
|
if testConfig.OutputFormat != "" {
|
|
config.OutputFormat = testConfig.OutputFormat
|
|
}
|
|
if testConfig.OutputDir != "" {
|
|
config.OutputDir = testConfig.OutputDir
|
|
}
|
|
config.Verbose = testConfig.Verbose
|
|
config.Quiet = testConfig.Quiet
|
|
}
|
|
|
|
// Set appropriate template path based on theme - embedded templates will handle resolution
|
|
config.Template = resolveThemeTemplate(config.Theme)
|
|
|
|
return config
|
|
}
|