mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-15 15:15:51 +01:00
1043 lines
28 KiB
Go
1043 lines
28 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// setupTestEnvironment creates a proper directory structure for tests
|
|
// It returns the cleanup function and the project root directory
|
|
func setupTestEnvironment(t *testing.T) (cleanup func(), projectRoot string) {
|
|
// Save current directory
|
|
originalDir, _ := os.Getwd()
|
|
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create the wails project structure within temp directory
|
|
projectRoot = filepath.Join(tmpDir, "wails")
|
|
v3Dir := filepath.Join(projectRoot, "v3")
|
|
releaseDir := filepath.Join(v3Dir, "tasks", "release")
|
|
|
|
// Create all necessary directories
|
|
err := os.MkdirAll(releaseDir, 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test directory structure: %v", err)
|
|
}
|
|
|
|
// Change to the release directory (where the script would run from)
|
|
os.Chdir(releaseDir)
|
|
|
|
// Return cleanup function and project root
|
|
cleanup = func() {
|
|
os.Chdir(originalDir)
|
|
}
|
|
return cleanup, projectRoot
|
|
}
|
|
|
|
func TestExtractChangelogContent_EmptySections(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a test file with only one section having content
|
|
testContent := `# Unreleased Changes
|
|
|
|
<!--
|
|
This file is used to collect changelog entries for the next v3-alpha release.
|
|
-->
|
|
|
|
## Added
|
|
<!-- New features, capabilities, or enhancements -->
|
|
|
|
## Changed
|
|
<!-- Changes in existing functionality -->
|
|
|
|
## Fixed
|
|
- Fix critical bug in the system
|
|
|
|
## Deprecated
|
|
<!-- Soon-to-be removed features -->
|
|
|
|
## Removed
|
|
<!-- Features removed in this release -->
|
|
|
|
## Security
|
|
<!-- Security-related changes -->
|
|
|
|
---
|
|
|
|
### Example Entries:
|
|
|
|
**Added:**
|
|
- Example entry that should not be included`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Extract the content
|
|
content, err := extractChangelogContent()
|
|
if err != nil {
|
|
t.Fatalf("extractChangelogContent() failed: %v", err)
|
|
}
|
|
|
|
// Verify we got content
|
|
if content == "" {
|
|
t.Fatal("Expected to extract content, but got empty string")
|
|
}
|
|
|
|
// Verify ONLY the Fixed section is included (the only one with content)
|
|
if !strings.Contains(content, "## Fixed") {
|
|
t.Error("Expected to find '## Fixed' section header")
|
|
}
|
|
|
|
if !strings.Contains(content, "Fix critical bug in the system") {
|
|
t.Error("Expected to find the Fixed content")
|
|
}
|
|
|
|
// Verify empty sections are NOT included
|
|
sections := []string{"## Added", "## Changed", "## Deprecated", "## Removed", "## Security"}
|
|
for _, section := range sections {
|
|
if strings.Contains(content, section) {
|
|
t.Errorf("Expected NOT to find empty section '%s'", section)
|
|
}
|
|
}
|
|
|
|
// Verify comments are not included
|
|
if strings.Contains(content, "<!--") || strings.Contains(content, "-->") {
|
|
t.Error("Expected NOT to find HTML comments")
|
|
}
|
|
|
|
// Verify example content is not included
|
|
if strings.Contains(content, "Example entry that should not be included") {
|
|
t.Error("Expected NOT to find example content")
|
|
}
|
|
}
|
|
|
|
func TestExtractChangelogContent_AllEmpty(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a test file with all empty sections (just the template)
|
|
testContent := getUnreleasedChangelogTemplate()
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Extract the content
|
|
content, err := extractChangelogContent()
|
|
if err != nil {
|
|
t.Fatalf("extractChangelogContent() failed: %v", err)
|
|
}
|
|
|
|
// Verify we got empty string (no content)
|
|
if content != "" {
|
|
t.Fatalf("Expected empty string for template-only file, got: %s", content)
|
|
}
|
|
}
|
|
|
|
func TestExtractChangelogContent_MixedSections(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a test file with some sections having content, others empty
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- New feature A
|
|
- New feature B
|
|
|
|
## Changed
|
|
<!-- No changes -->
|
|
|
|
## Fixed
|
|
<!-- No fixes -->
|
|
|
|
## Deprecated
|
|
- Deprecated feature X
|
|
|
|
## Removed
|
|
<!-- Nothing removed -->
|
|
|
|
## Security
|
|
- Security fix for CVE-2024-1234`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Extract the content
|
|
content, err := extractChangelogContent()
|
|
if err != nil {
|
|
t.Fatalf("extractChangelogContent() failed: %v", err)
|
|
}
|
|
|
|
// Verify we got content
|
|
if content == "" {
|
|
t.Fatal("Expected to extract content, but got empty string")
|
|
}
|
|
|
|
// Verify sections WITH content are included
|
|
if !strings.Contains(content, "## Added") {
|
|
t.Error("Expected to find '## Added' section header")
|
|
}
|
|
if !strings.Contains(content, "New feature A") {
|
|
t.Error("Expected to find Added content")
|
|
}
|
|
|
|
if !strings.Contains(content, "## Deprecated") {
|
|
t.Error("Expected to find '## Deprecated' section header")
|
|
}
|
|
if !strings.Contains(content, "Deprecated feature X") {
|
|
t.Error("Expected to find Deprecated content")
|
|
}
|
|
|
|
if !strings.Contains(content, "## Security") {
|
|
t.Error("Expected to find '## Security' section header")
|
|
}
|
|
if !strings.Contains(content, "Security fix for CVE-2024-1234") {
|
|
t.Error("Expected to find Security content")
|
|
}
|
|
|
|
// Verify empty sections are NOT included
|
|
emptyHeaders := []string{"## Changed", "## Fixed", "## Removed"}
|
|
for _, header := range emptyHeaders {
|
|
if strings.Contains(content, header) {
|
|
t.Errorf("Expected NOT to find empty section '%s'", header)
|
|
}
|
|
}
|
|
|
|
// Print the extracted content for debugging
|
|
t.Logf("Extracted content:\n%s", content)
|
|
}
|
|
|
|
func TestGetUnreleasedChangelogTemplate(t *testing.T) {
|
|
template := getUnreleasedChangelogTemplate()
|
|
|
|
// Check that template contains required sections
|
|
requiredSections := []string{
|
|
"# Unreleased Changes",
|
|
"## Added",
|
|
"## Changed",
|
|
"## Fixed",
|
|
"## Deprecated",
|
|
"## Removed",
|
|
"## Security",
|
|
"### Example Entries",
|
|
}
|
|
|
|
for _, section := range requiredSections {
|
|
if !strings.Contains(template, section) {
|
|
t.Errorf("Template missing required section: %s", section)
|
|
}
|
|
}
|
|
|
|
// Check that template contains guidelines
|
|
if !strings.Contains(template, "Guidelines:") {
|
|
t.Error("Template missing guidelines section")
|
|
}
|
|
|
|
// Check that template contains example entries
|
|
if !strings.Contains(template, "Add support for custom window icons") {
|
|
t.Error("Template missing example entries")
|
|
}
|
|
}
|
|
|
|
func TestHasUnreleasedContent_WithContent(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a file with actual content
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- Add new feature for testing
|
|
- Add another important feature
|
|
|
|
## Fixed
|
|
- Fix critical bug in system`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
|
|
if !hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return true for file with content")
|
|
}
|
|
}
|
|
|
|
func TestHasUnreleasedContent_WithoutContent(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a file with only template content (no actual entries)
|
|
template := getUnreleasedChangelogTemplate()
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(template), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
|
|
if hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return false for template-only file")
|
|
}
|
|
}
|
|
|
|
func TestHasUnreleasedContent_WithEmptyBullets(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a file with empty bullet points
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
-
|
|
-
|
|
|
|
## Fixed
|
|
<!-- No content -->`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
|
|
if hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return false for file with empty bullets")
|
|
}
|
|
}
|
|
|
|
func TestHasUnreleasedContent_NonexistentFile(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Don't create the file
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err == nil {
|
|
t.Error("Expected hasUnreleasedContent() to return an error for nonexistent file")
|
|
}
|
|
|
|
if hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return false for nonexistent file")
|
|
}
|
|
}
|
|
|
|
func TestSafeFileOperation_Success(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
testFile := filepath.Join(tmpDir, "test.txt")
|
|
|
|
// Create initial file
|
|
initialContent := "initial content"
|
|
err := os.WriteFile(testFile, []byte(initialContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Perform safe operation that succeeds
|
|
newContent := "new content"
|
|
err = safeFileOperation(testFile, func() error {
|
|
return os.WriteFile(testFile, []byte(newContent), 0o644)
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("safeFileOperation() failed: %v", err)
|
|
}
|
|
|
|
// Verify the file has new content
|
|
content, err := os.ReadFile(testFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file after operation: %v", err)
|
|
}
|
|
|
|
if string(content) != newContent {
|
|
t.Errorf("Expected file content '%s', got '%s'", newContent, string(content))
|
|
}
|
|
|
|
// Verify backup file was cleaned up
|
|
backupFile := testFile + ".backup"
|
|
if _, err := os.Stat(backupFile); !os.IsNotExist(err) {
|
|
t.Error("Backup file was not cleaned up after successful operation")
|
|
}
|
|
}
|
|
|
|
func TestSafeFileOperation_Failure(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
testFile := filepath.Join(tmpDir, "test.txt")
|
|
|
|
// Create initial file
|
|
initialContent := "initial content"
|
|
err := os.WriteFile(testFile, []byte(initialContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Perform safe operation that fails
|
|
err = safeFileOperation(testFile, func() error {
|
|
// First write something to the file
|
|
os.WriteFile(testFile, []byte("corrupted content"), 0o644)
|
|
// Then return an error to simulate failure
|
|
return os.ErrInvalid
|
|
})
|
|
|
|
if err == nil {
|
|
t.Error("Expected safeFileOperation() to return error")
|
|
}
|
|
|
|
// Verify the file was restored to original content
|
|
content, err := os.ReadFile(testFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file after failed operation: %v", err)
|
|
}
|
|
|
|
if string(content) != initialContent {
|
|
t.Errorf("Expected file content to be restored to '%s', got '%s'", initialContent, string(content))
|
|
}
|
|
|
|
// Verify backup file was cleaned up
|
|
backupFile := testFile + ".backup"
|
|
if _, err := os.Stat(backupFile); !os.IsNotExist(err) {
|
|
t.Error("Backup file was not cleaned up after failed operation")
|
|
}
|
|
}
|
|
|
|
func TestSafeFileOperation_NewFile(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
testFile := filepath.Join(tmpDir, "newfile.txt")
|
|
|
|
// Perform safe operation on non-existent file
|
|
content := "new file content"
|
|
err := safeFileOperation(testFile, func() error {
|
|
return os.WriteFile(testFile, []byte(content), 0o644)
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("safeFileOperation() failed: %v", err)
|
|
}
|
|
|
|
// Verify the file was created with correct content
|
|
fileContent, err := os.ReadFile(testFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read created file: %v", err)
|
|
}
|
|
|
|
if string(fileContent) != content {
|
|
t.Errorf("Expected file content '%s', got '%s'", content, string(fileContent))
|
|
}
|
|
}
|
|
|
|
func TestCopyFile(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
srcFile := filepath.Join(tmpDir, "source.txt")
|
|
dstFile := filepath.Join(tmpDir, "destination.txt")
|
|
|
|
// Create source file
|
|
content := "test content for copying"
|
|
err := os.WriteFile(srcFile, []byte(content), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create source file: %v", err)
|
|
}
|
|
|
|
// Copy the file
|
|
err = copyFile(srcFile, dstFile)
|
|
if err != nil {
|
|
t.Fatalf("copyFile() failed: %v", err)
|
|
}
|
|
|
|
// Verify destination file exists and has correct content
|
|
dstContent, err := os.ReadFile(dstFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read destination file: %v", err)
|
|
}
|
|
|
|
if string(dstContent) != content {
|
|
t.Errorf("Expected destination content '%s', got '%s'", content, string(dstContent))
|
|
}
|
|
}
|
|
|
|
func TestCopyFile_NonexistentSource(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
srcFile := filepath.Join(tmpDir, "nonexistent.txt")
|
|
dstFile := filepath.Join(tmpDir, "destination.txt")
|
|
|
|
// Try to copy non-existent file
|
|
err := copyFile(srcFile, dstFile)
|
|
if err == nil {
|
|
t.Error("Expected copyFile() to return error for non-existent source")
|
|
}
|
|
|
|
// Verify destination file was not created
|
|
if _, err := os.Stat(dstFile); !os.IsNotExist(err) {
|
|
t.Error("Destination file should not exist after failed copy")
|
|
}
|
|
}
|
|
|
|
func TestUpdateVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
currentVersion string
|
|
expectedVersion string
|
|
}{
|
|
{
|
|
name: "Alpha version increment",
|
|
currentVersion: "v3.0.0-alpha.12",
|
|
expectedVersion: "v3.0.0-alpha.13",
|
|
},
|
|
{
|
|
name: "Beta version increment",
|
|
currentVersion: "v3.0.0-beta.5",
|
|
expectedVersion: "v3.0.0-beta.6",
|
|
},
|
|
{
|
|
name: "RC version increment",
|
|
currentVersion: "v2.5.0-rc.1",
|
|
expectedVersion: "v2.5.0-rc.2",
|
|
},
|
|
{
|
|
name: "Patch version increment",
|
|
currentVersion: "v3.0.0",
|
|
expectedVersion: "v3.0.1",
|
|
},
|
|
{
|
|
name: "Patch version with higher number",
|
|
currentVersion: "v1.2.15",
|
|
expectedVersion: "v1.2.16",
|
|
},
|
|
{
|
|
name: "Pre-release without number becomes patch",
|
|
currentVersion: "v3.0.0-alpha",
|
|
expectedVersion: "v3.0.1",
|
|
},
|
|
{
|
|
name: "Version without v prefix",
|
|
currentVersion: "3.0.0",
|
|
expectedVersion: "v3.0.1",
|
|
},
|
|
{
|
|
name: "Alpha with large number",
|
|
currentVersion: "v3.0.0-alpha.999",
|
|
expectedVersion: "v3.0.0-alpha.1000",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a temporary directory for this test
|
|
tmpDir := t.TempDir()
|
|
tempVersionFile := filepath.Join(tmpDir, "version.txt")
|
|
|
|
// Save original versionFile path
|
|
originalVersionFile := versionFile
|
|
defer func() {
|
|
// Restore original value
|
|
_ = originalVersionFile
|
|
}()
|
|
|
|
// Write the current version to temp file
|
|
err := os.WriteFile(tempVersionFile, []byte(tt.currentVersion), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test version file: %v", err)
|
|
}
|
|
|
|
// Test the updateVersion function logic directly
|
|
result := func() string {
|
|
currentVersionData, err := os.ReadFile(tempVersionFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read version file: %v", err)
|
|
}
|
|
currentVersion := strings.TrimSpace(string(currentVersionData))
|
|
|
|
// Check if it has a pre-release suffix (e.g., -alpha.12, -beta.1)
|
|
if strings.Contains(currentVersion, "-") {
|
|
// Split on the dash to separate version and pre-release
|
|
parts := strings.SplitN(currentVersion, "-", 2)
|
|
baseVersion := parts[0]
|
|
preRelease := parts[1]
|
|
|
|
// Check if pre-release has a numeric suffix (e.g., alpha.12)
|
|
lastDotIndex := strings.LastIndex(preRelease, ".")
|
|
if lastDotIndex != -1 {
|
|
preReleaseTag := preRelease[:lastDotIndex]
|
|
numberStr := preRelease[lastDotIndex+1:]
|
|
|
|
// Try to parse the number
|
|
if number, err := strconv.Atoi(numberStr); err == nil {
|
|
// Increment the pre-release number
|
|
number++
|
|
newVersion := fmt.Sprintf("%s-%s.%d", baseVersion, preReleaseTag, number)
|
|
return newVersion
|
|
}
|
|
}
|
|
|
|
// If we can't parse the pre-release format, just increment patch version
|
|
// and remove pre-release suffix
|
|
return testIncrementPatchVersion(baseVersion)
|
|
}
|
|
|
|
// No pre-release suffix, just increment patch version
|
|
return testIncrementPatchVersion(currentVersion)
|
|
}()
|
|
|
|
if result != tt.expectedVersion {
|
|
t.Errorf("updateVersion() = %v, want %v", result, tt.expectedVersion)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// testIncrementPatchVersion is a test version of incrementPatchVersion that doesn't write to file
|
|
func testIncrementPatchVersion(version string) string {
|
|
// Remove 'v' prefix if present
|
|
versionWithoutV := strings.TrimPrefix(version, "v")
|
|
|
|
// Split into major.minor.patch
|
|
parts := strings.Split(versionWithoutV, ".")
|
|
if len(parts) != 3 {
|
|
// Not a valid semver, return as-is
|
|
return version
|
|
}
|
|
|
|
// Parse patch version
|
|
patch, err := strconv.Atoi(parts[2])
|
|
if err != nil {
|
|
return version
|
|
}
|
|
|
|
// Increment patch
|
|
patch++
|
|
|
|
// Reconstruct version
|
|
return fmt.Sprintf("v%s.%s.%d", parts[0], parts[1], patch)
|
|
}
|
|
|
|
// extractTestContent is a test helper that extracts changelog content using the same logic as extractChangelogContent
|
|
func extractTestContent(contentStr string) string {
|
|
lines := strings.Split(contentStr, "\n")
|
|
|
|
var result []string
|
|
var inExampleSection bool
|
|
var inCommentBlock bool
|
|
var hasActualContent bool
|
|
var currentSection string
|
|
|
|
for i, line := range lines {
|
|
trimmedLine := strings.TrimSpace(line)
|
|
|
|
// Track comment blocks (handle multi-line comments)
|
|
if strings.Contains(line, "<!--") {
|
|
inCommentBlock = true
|
|
// Check if comment ends on same line
|
|
if strings.Contains(line, "-->") {
|
|
inCommentBlock = false
|
|
}
|
|
continue
|
|
}
|
|
if inCommentBlock {
|
|
if strings.Contains(line, "-->") {
|
|
inCommentBlock = false
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Skip the main title
|
|
if strings.HasPrefix(trimmedLine, "# Unreleased Changes") {
|
|
continue
|
|
}
|
|
|
|
// Check if we're entering the example section
|
|
if strings.HasPrefix(trimmedLine, "---") || strings.HasPrefix(trimmedLine, "### Example Entries") {
|
|
inExampleSection = true
|
|
continue
|
|
}
|
|
|
|
// Skip example section content
|
|
if inExampleSection {
|
|
continue
|
|
}
|
|
|
|
// Handle section headers
|
|
if strings.HasPrefix(trimmedLine, "##") {
|
|
currentSection = trimmedLine
|
|
// Only include section headers that have content after them
|
|
// We'll add it later if we find content
|
|
continue
|
|
}
|
|
|
|
// Handle bullet points
|
|
if strings.HasPrefix(trimmedLine, "-") || strings.HasPrefix(trimmedLine, "*") {
|
|
// Check if this is actual content (not empty)
|
|
content := strings.TrimSpace(trimmedLine[1:])
|
|
if content != "" {
|
|
// If this is the first content in a section, add the section header first
|
|
if currentSection != "" {
|
|
// Only add empty line if this isn't the first section
|
|
if len(result) > 0 {
|
|
result = append(result, "")
|
|
}
|
|
result = append(result, currentSection)
|
|
currentSection = "" // Reset so we don't add it again
|
|
}
|
|
result = append(result, line)
|
|
hasActualContent = true
|
|
}
|
|
} else if trimmedLine != "" && !strings.HasPrefix(trimmedLine, "<!--") {
|
|
// Include other non-empty, non-comment lines that aren't section headers
|
|
if !strings.HasPrefix(trimmedLine, "##") {
|
|
// Check if next line exists and is not a comment placeholder
|
|
if i+1 < len(lines) {
|
|
nextLine := strings.TrimSpace(lines[i+1])
|
|
if !strings.HasPrefix(nextLine, "<!--") {
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasActualContent {
|
|
return ""
|
|
}
|
|
|
|
// Clean up result - remove any trailing empty lines
|
|
for len(result) > 0 && strings.TrimSpace(result[len(result)-1]) == "" {
|
|
result = result[:len(result)-1]
|
|
}
|
|
|
|
return strings.Join(result, "\n")
|
|
}
|
|
|
|
func TestHasUnreleasedContent_IgnoresExampleSection(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a file with content only in the example section
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
<!-- No content -->
|
|
|
|
## Fixed
|
|
<!-- No content -->
|
|
|
|
---
|
|
|
|
### Example Entries:
|
|
|
|
**Added:**
|
|
- Add support for custom window icons in application options
|
|
- Add new SetWindowIcon() method to runtime API (#1234)`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
|
|
if hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return false when content is only in example section")
|
|
}
|
|
}
|
|
|
|
func TestHasUnreleasedContent_WithMixedContent(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a file with both real content and example content
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- Real feature addition here
|
|
|
|
## Fixed
|
|
<!-- No content -->
|
|
|
|
---
|
|
|
|
### Example Entries:
|
|
|
|
**Added:**
|
|
- Add support for custom window icons in application options`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
|
|
if !hasContent {
|
|
t.Error("Expected hasUnreleasedContent() to return true when file has real content")
|
|
}
|
|
}
|
|
|
|
// Integration test for the complete cleanup workflow
|
|
func TestCleanupWorkflow_Integration(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a changelog file with actual content
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- Add comprehensive changelog processing system
|
|
- Add validation for Keep a Changelog format compliance
|
|
|
|
## Fixed
|
|
- Fix parsing issues with various markdown bullet styles
|
|
- Fix validation edge cases for empty content sections`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Step 1: Check that file has content
|
|
hasContent, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed: %v", err)
|
|
}
|
|
if !hasContent {
|
|
t.Fatal("Expected file to have content")
|
|
}
|
|
|
|
// Step 2: Perform safe cleanup operation
|
|
err = safeFileOperation(unreleasedChangelogFile, func() error {
|
|
return clearUnreleasedChangelog()
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Safe cleanup operation failed: %v", err)
|
|
}
|
|
|
|
// Step 3: Verify file was reset to template
|
|
content, err := os.ReadFile(unreleasedChangelogFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file after cleanup: %v", err)
|
|
}
|
|
|
|
template := getUnreleasedChangelogTemplate()
|
|
if string(content) != template {
|
|
t.Error("File was not properly reset to template")
|
|
}
|
|
|
|
// Step 4: Verify original content is gone
|
|
if strings.Contains(string(content), "Add comprehensive changelog processing system") {
|
|
t.Error("Original content still present after cleanup")
|
|
}
|
|
|
|
// Step 5: Verify file no longer has content
|
|
hasContentAfter, err := hasUnreleasedContent()
|
|
if err != nil {
|
|
t.Fatalf("hasUnreleasedContent() failed after cleanup: %v", err)
|
|
}
|
|
if hasContentAfter {
|
|
t.Error("File should not have content after cleanup")
|
|
}
|
|
}
|
|
|
|
func TestFullReleaseWorkflow_OnlyNonEmptySections(t *testing.T) {
|
|
cleanup, projectRoot := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create subdirectories to match expected structure
|
|
err := os.MkdirAll(filepath.Join(projectRoot, "v3", "internal", "version"), 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create version directory: %v", err)
|
|
}
|
|
|
|
err = os.MkdirAll(filepath.Join(projectRoot, "docs", "src", "content", "docs"), 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create docs directory: %v", err)
|
|
}
|
|
|
|
// Create version file
|
|
versionFile := filepath.Join(projectRoot, "v3", "internal", "version", "version.txt")
|
|
err = os.WriteFile(versionFile, []byte("v1.0.0-alpha.5"), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create version file: %v", err)
|
|
}
|
|
|
|
// Create initial changelog
|
|
changelogFile := filepath.Join(projectRoot, "docs", "src", "content", "docs", "changelog.mdx")
|
|
initialChangelog := `---
|
|
title: Changelog
|
|
---
|
|
|
|
## [Unreleased]
|
|
|
|
## v1.0.0-alpha.4 - 2024-01-01
|
|
|
|
### Added
|
|
- Previous feature
|
|
|
|
`
|
|
err = os.WriteFile(changelogFile, []byte(initialChangelog), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create changelog file: %v", err)
|
|
}
|
|
|
|
// Create UNRELEASED_CHANGELOG.md with mixed content
|
|
unreleasedContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- New amazing feature
|
|
- Another cool addition
|
|
|
|
## Changed
|
|
<!-- No changes -->
|
|
|
|
## Fixed
|
|
<!-- No fixes -->
|
|
|
|
## Deprecated
|
|
- Old API method
|
|
|
|
## Removed
|
|
<!-- Nothing removed -->
|
|
|
|
## Security
|
|
<!-- No security updates -->
|
|
`
|
|
// The script expects the file at ../../UNRELEASED_CHANGELOG.md relative to release dir
|
|
unreleasedFile := filepath.Join(projectRoot, "v3", "UNRELEASED_CHANGELOG.md")
|
|
err = os.WriteFile(unreleasedFile, []byte(unreleasedContent), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create unreleased changelog: %v", err)
|
|
}
|
|
|
|
// Run the release process simulation
|
|
// Read and process the files manually since we can't override constants
|
|
content, err := os.ReadFile(unreleasedFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read unreleased file: %v", err)
|
|
}
|
|
|
|
// Extract content using the same logic as extractChangelogContent
|
|
changelogContent := extractTestContent(string(content))
|
|
if changelogContent == "" {
|
|
t.Fatal("Failed to extract any content")
|
|
}
|
|
|
|
// Verify only non-empty sections were extracted
|
|
if !strings.Contains(changelogContent, "## Added") {
|
|
t.Error("Expected '## Added' section to be included")
|
|
}
|
|
if !strings.Contains(changelogContent, "## Deprecated") {
|
|
t.Error("Expected '## Deprecated' section to be included")
|
|
}
|
|
|
|
// Verify empty sections were NOT extracted
|
|
emptySections := []string{"## Changed", "## Fixed", "## Removed", "## Security"}
|
|
for _, section := range emptySections {
|
|
if strings.Contains(changelogContent, section) {
|
|
t.Errorf("Expected empty section '%s' to NOT be included", section)
|
|
}
|
|
}
|
|
|
|
// Simulate updating the main changelog
|
|
changelogData, _ := os.ReadFile(changelogFile)
|
|
changelog := string(changelogData)
|
|
changelogSplit := strings.Split(changelog, "## [Unreleased]")
|
|
|
|
newVersion := "v1.0.0-alpha.6"
|
|
today := "2024-01-15"
|
|
newChangelog := changelogSplit[0] + "## [Unreleased]\n\n## " + newVersion + " - " + today + "\n\n" + changelogContent + changelogSplit[1]
|
|
|
|
// Verify the final changelog format
|
|
if !strings.Contains(newChangelog, "## v1.0.0-alpha.6 - 2024-01-15") {
|
|
t.Error("Expected new version header in changelog")
|
|
}
|
|
|
|
// Count occurrences of section headers in the new version section
|
|
newVersionSection := strings.Split(newChangelog, "## v1.0.0-alpha.4")[0]
|
|
|
|
addedCount := strings.Count(newVersionSection, "## Added")
|
|
if addedCount != 1 {
|
|
t.Errorf("Expected exactly 1 '## Added' section, got %d", addedCount)
|
|
}
|
|
|
|
deprecatedCount := strings.Count(newVersionSection, "## Deprecated")
|
|
if deprecatedCount != 1 {
|
|
t.Errorf("Expected exactly 1 '## Deprecated' section, got %d", deprecatedCount)
|
|
}
|
|
|
|
// Ensure no empty sections in the new version section
|
|
for _, section := range []string{"## Changed", "## Fixed", "## Removed", "## Security"} {
|
|
count := strings.Count(newVersionSection, section)
|
|
if count > 0 {
|
|
t.Errorf("Expected 0 occurrences of empty section '%s', got %d", section, count)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test error handling during cleanup
|
|
func TestCleanupWorkflow_ErrorHandling(t *testing.T) {
|
|
cleanup, _ := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Create a changelog file with content
|
|
testContent := `# Unreleased Changes
|
|
|
|
## Added
|
|
- Test content that should be preserved on error`
|
|
|
|
err := os.WriteFile(unreleasedChangelogFile, []byte(testContent), 0o644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
// Simulate an error during cleanup by making the operation fail
|
|
err = safeFileOperation(unreleasedChangelogFile, func() error {
|
|
// First corrupt the file
|
|
os.WriteFile(unreleasedChangelogFile, []byte("corrupted"), 0o644)
|
|
// Then return an error
|
|
return os.ErrPermission
|
|
})
|
|
|
|
if err == nil {
|
|
t.Error("Expected safeFileOperation to return error")
|
|
}
|
|
|
|
// Verify original content was restored
|
|
content, err := os.ReadFile(unreleasedChangelogFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file after error: %v", err)
|
|
}
|
|
|
|
if string(content) != testContent {
|
|
t.Error("Original content was not restored after error")
|
|
}
|
|
}
|