mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
* feat: ignore vendored directories * chore: cr tweaks * fix: sonarcloud detected issues * fix: sonarcloud detected issues
688 lines
18 KiB
Go
688 lines
18 KiB
Go
package internal
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/ivuorinen/gh-action-readme/appconstants"
|
|
"github.com/ivuorinen/gh-action-readme/testutil"
|
|
)
|
|
|
|
func TestGenerator_NewGenerator(t *testing.T) {
|
|
t.Parallel()
|
|
config := &AppConfig{
|
|
Theme: "default",
|
|
OutputFormat: "md",
|
|
OutputDir: ".",
|
|
Verbose: false,
|
|
Quiet: false,
|
|
}
|
|
|
|
generator := NewGenerator(config)
|
|
|
|
if generator == nil {
|
|
t.Fatal("expected generator to be created")
|
|
}
|
|
|
|
if generator.Config != config {
|
|
t.Error("expected generator to have the provided config")
|
|
}
|
|
|
|
if generator.Output == nil {
|
|
t.Error("expected generator to have output initialized")
|
|
}
|
|
}
|
|
|
|
func TestGenerator_DiscoverActionFiles(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T, tmpDir string)
|
|
recursive bool
|
|
expectedLen int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "single action.yml in root",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteActionFixture(t, tmpDir, appconstants.TestFixtureJavaScriptSimple)
|
|
},
|
|
recursive: false,
|
|
expectedLen: 1,
|
|
},
|
|
{
|
|
name: "action.yaml variant",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteActionFixtureAs(
|
|
t,
|
|
tmpDir,
|
|
appconstants.ActionFileNameYAML,
|
|
appconstants.TestFixtureJavaScriptSimple,
|
|
)
|
|
},
|
|
recursive: false,
|
|
expectedLen: 1,
|
|
},
|
|
{
|
|
name: "both yml and yaml files",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteActionFixture(t, tmpDir, appconstants.TestFixtureJavaScriptSimple)
|
|
testutil.WriteActionFixtureAs(
|
|
t,
|
|
tmpDir,
|
|
appconstants.ActionFileNameYAML,
|
|
appconstants.TestFixtureMinimalAction,
|
|
)
|
|
},
|
|
recursive: false,
|
|
expectedLen: 2,
|
|
},
|
|
{
|
|
name: "recursive discovery",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteActionFixture(t, tmpDir, appconstants.TestFixtureJavaScriptSimple)
|
|
testutil.CreateActionSubdir(
|
|
t,
|
|
tmpDir,
|
|
appconstants.TestDirSubdir,
|
|
appconstants.TestFixtureCompositeBasic,
|
|
)
|
|
},
|
|
recursive: true,
|
|
expectedLen: 2,
|
|
},
|
|
{
|
|
name: "non-recursive skips subdirectories",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteActionFixture(t, tmpDir, appconstants.TestFixtureJavaScriptSimple)
|
|
testutil.CreateActionSubdir(
|
|
t,
|
|
tmpDir,
|
|
appconstants.TestDirSubdir,
|
|
appconstants.TestFixtureCompositeBasic,
|
|
)
|
|
},
|
|
recursive: false,
|
|
expectedLen: 1,
|
|
},
|
|
{
|
|
name: "no action files",
|
|
setupFunc: func(t *testing.T, tmpDir string) {
|
|
t.Helper()
|
|
testutil.WriteTestFile(t, filepath.Join(tmpDir, "README.md"), "# Test")
|
|
},
|
|
recursive: false,
|
|
expectedLen: 0,
|
|
},
|
|
{
|
|
name: "nonexistent directory",
|
|
setupFunc: nil,
|
|
recursive: false,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
config := &AppConfig{Quiet: true}
|
|
generator := NewGenerator(config)
|
|
|
|
testDir := tmpDir
|
|
if tt.setupFunc != nil {
|
|
tt.setupFunc(t, tmpDir)
|
|
} else if tt.expectError {
|
|
testDir = filepath.Join(tmpDir, "nonexistent")
|
|
}
|
|
|
|
files, err := generator.DiscoverActionFiles(testDir, tt.recursive, []string{})
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
testutil.AssertNoError(t, err)
|
|
testutil.AssertEqual(t, tt.expectedLen, len(files))
|
|
|
|
// Verify all returned files exist and are action files
|
|
for _, file := range files {
|
|
testutil.AssertFileExists(t, file)
|
|
|
|
if !strings.HasSuffix(file, appconstants.ActionFileNameYML) &&
|
|
!strings.HasSuffix(file, appconstants.ActionFileNameYAML) {
|
|
t.Errorf("discovered file is not an action file: %s", file)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerator_GenerateFromFile(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
actionYML string
|
|
outputFormat string
|
|
expectError bool
|
|
contains []string
|
|
}{
|
|
{
|
|
name: "simple action to markdown",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
outputFormat: "md",
|
|
expectError: false,
|
|
contains: []string{"# Simple JavaScript Action", "A simple JavaScript action for testing"},
|
|
},
|
|
{
|
|
name: "composite action to markdown",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureCompositeBasic),
|
|
outputFormat: "md",
|
|
expectError: false,
|
|
contains: []string{"# Basic Composite Action", "A simple composite action with basic steps"},
|
|
},
|
|
{
|
|
name: "action to HTML",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
outputFormat: "html",
|
|
expectError: false,
|
|
contains: []string{
|
|
"Simple JavaScript Action",
|
|
"A simple JavaScript action for testing",
|
|
}, // HTML uses same template content
|
|
},
|
|
{
|
|
name: "action to JSON",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
outputFormat: "json",
|
|
expectError: false,
|
|
contains: []string{
|
|
`"name": "Simple JavaScript Action"`,
|
|
`"description": "A simple JavaScript action for testing"`,
|
|
},
|
|
},
|
|
{
|
|
name: "invalid action file",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureInvalidInvalidUsing),
|
|
outputFormat: "md",
|
|
expectError: true, // Invalid runtime configuration should cause failure
|
|
contains: []string{},
|
|
},
|
|
{
|
|
name: "unknown output format",
|
|
actionYML: testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
outputFormat: "unknown",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
// Set up test templates
|
|
testutil.SetupTestTemplates(t, tmpDir)
|
|
|
|
// Write action file
|
|
actionPath := filepath.Join(tmpDir, appconstants.ActionFileNameYML)
|
|
testutil.WriteTestFile(t, actionPath, tt.actionYML)
|
|
|
|
// Create generator with explicit template path
|
|
config := &AppConfig{
|
|
OutputFormat: tt.outputFormat,
|
|
OutputDir: tmpDir,
|
|
Quiet: true,
|
|
Template: filepath.Join(tmpDir, "templates", "readme.tmpl"),
|
|
}
|
|
generator := NewGenerator(config)
|
|
|
|
// Generate output
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
testutil.AssertNoError(t, err)
|
|
|
|
// Find the generated output file based on format
|
|
var pattern string
|
|
switch tt.outputFormat {
|
|
case "html":
|
|
pattern = filepath.Join(tmpDir, "*.html")
|
|
case "json":
|
|
pattern = filepath.Join(tmpDir, "*.json")
|
|
default:
|
|
pattern = filepath.Join(tmpDir, "README*.md")
|
|
}
|
|
readmeFiles, _ := filepath.Glob(pattern)
|
|
if len(readmeFiles) == 0 {
|
|
t.Errorf("no output file was created for format %s", tt.outputFormat)
|
|
|
|
return
|
|
}
|
|
|
|
// Read and verify output content
|
|
content, err := os.ReadFile(readmeFiles[0])
|
|
testutil.AssertNoError(t, err)
|
|
|
|
contentStr := string(content)
|
|
for _, expectedStr := range tt.contains {
|
|
if !strings.Contains(contentStr, expectedStr) {
|
|
t.Errorf("output does not contain expected string %q", expectedStr)
|
|
t.Logf("Output content: %s", contentStr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// countREADMEFiles counts README.md files in a directory tree.
|
|
func countREADMEFiles(t *testing.T, dir string) int {
|
|
t.Helper()
|
|
count := 0
|
|
err := filepath.Walk(dir, func(path string, _ os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.HasSuffix(path, "README.md") {
|
|
count++
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Errorf("error walking directory: %v", err)
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// logREADMELocations logs the locations of README files for debugging.
|
|
func logREADMELocations(t *testing.T, dir string) {
|
|
t.Helper()
|
|
_ = filepath.Walk(dir, func(path string, _ os.FileInfo, err error) error {
|
|
if err == nil && strings.HasSuffix(path, "README.md") {
|
|
t.Logf("Found README at: %s", path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGenerator_ProcessBatch(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T, tmpDir string) []string
|
|
expectError bool
|
|
expectFiles int
|
|
}{
|
|
{
|
|
name: "process multiple valid files",
|
|
setupFunc: func(t *testing.T, tmpDir string) []string {
|
|
t.Helper()
|
|
// Create separate directories for each action
|
|
dirs := createTestDirs(t, tmpDir, "action1", "action2")
|
|
|
|
files := []string{
|
|
filepath.Join(dirs[0], appconstants.ActionFileNameYML),
|
|
filepath.Join(dirs[1], appconstants.ActionFileNameYML),
|
|
}
|
|
testutil.WriteTestFile(t, files[0], testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple))
|
|
testutil.WriteTestFile(t, files[1], testutil.MustReadFixture(appconstants.TestFixtureCompositeBasic))
|
|
|
|
return files
|
|
},
|
|
expectError: false,
|
|
expectFiles: 2,
|
|
},
|
|
{
|
|
name: "handle mixed valid and invalid files",
|
|
setupFunc: func(t *testing.T, tmpDir string) []string {
|
|
t.Helper()
|
|
// Create separate directories for mixed test too
|
|
dirs := createTestDirs(t, tmpDir, "valid-action", "invalid-action")
|
|
|
|
files := []string{
|
|
filepath.Join(dirs[0], appconstants.ActionFileNameYML),
|
|
filepath.Join(dirs[1], appconstants.ActionFileNameYML),
|
|
}
|
|
testutil.WriteTestFile(t, files[0], testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple))
|
|
testutil.WriteTestFile(
|
|
t,
|
|
files[1],
|
|
testutil.MustReadFixture(appconstants.TestFixtureInvalidInvalidUsing),
|
|
)
|
|
|
|
return files
|
|
},
|
|
expectError: true, // Invalid runtime configuration should cause batch to fail
|
|
expectFiles: 0, // No files should be expected when batch fails
|
|
},
|
|
{
|
|
name: "empty file list",
|
|
setupFunc: func(_ *testing.T, _ string) []string {
|
|
return []string{}
|
|
},
|
|
expectError: true, // ProcessBatch returns error for empty list
|
|
expectFiles: 0,
|
|
},
|
|
{
|
|
name: "nonexistent files",
|
|
setupFunc: func(_ *testing.T, tmpDir string) []string {
|
|
return []string{filepath.Join(tmpDir, "nonexistent.yml")}
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
// Set up test templates
|
|
testutil.SetupTestTemplates(t, tmpDir)
|
|
|
|
config := &AppConfig{
|
|
OutputFormat: "md",
|
|
// Don't set OutputDir so each action generates README in its own directory
|
|
Verbose: true, // Enable verbose to see what's happening
|
|
Template: filepath.Join(tmpDir, "templates", "readme.tmpl"),
|
|
}
|
|
generator := NewGenerator(config)
|
|
|
|
files := tt.setupFunc(t, tmpDir)
|
|
err := generator.ProcessBatch(files)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Count generated README files
|
|
if tt.expectFiles > 0 {
|
|
readmeCount := countREADMEFiles(t, tmpDir)
|
|
if readmeCount != tt.expectFiles {
|
|
t.Errorf("expected %d README files, got %d", tt.expectFiles, readmeCount)
|
|
t.Logf("Expected %d files, found %d", tt.expectFiles, readmeCount)
|
|
logREADMELocations(t, tmpDir)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerator_ValidateFiles(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T, tmpDir string) []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "all valid files",
|
|
setupFunc: func(t *testing.T, tmpDir string) []string {
|
|
t.Helper()
|
|
files := []string{
|
|
filepath.Join(tmpDir, "action1.yml"),
|
|
filepath.Join(tmpDir, "action2.yml"),
|
|
}
|
|
testutil.WriteTestFile(t, files[0], testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple))
|
|
testutil.WriteTestFile(t, files[1], testutil.MustReadFixture(appconstants.TestFixtureMinimalAction))
|
|
|
|
return files
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "files with validation issues",
|
|
setupFunc: func(t *testing.T, tmpDir string) []string {
|
|
t.Helper()
|
|
files := []string{
|
|
filepath.Join(tmpDir, "valid.yml"),
|
|
filepath.Join(tmpDir, "invalid.yml"),
|
|
}
|
|
testutil.WriteTestFile(t, files[0], testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple))
|
|
testutil.WriteTestFile(
|
|
t,
|
|
files[1],
|
|
testutil.MustReadFixture(appconstants.TestFixtureInvalidMissingDescription),
|
|
)
|
|
|
|
return files
|
|
},
|
|
expectError: true, // Validation should fail for invalid runtime configuration
|
|
},
|
|
{
|
|
name: "nonexistent files",
|
|
setupFunc: func(_ *testing.T, tmpDir string) []string {
|
|
return []string{filepath.Join(tmpDir, "nonexistent.yml")}
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
config := &AppConfig{Quiet: true}
|
|
generator := NewGenerator(config)
|
|
|
|
files := tt.setupFunc(t, tmpDir)
|
|
err := generator.ValidateFiles(files)
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
} else {
|
|
testutil.AssertNoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerator_CreateDependencyAnalyzer(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "with GitHub token",
|
|
token: "test-token",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "without GitHub token",
|
|
token: "",
|
|
expectError: false, // Should not error, but analyzer might have limitations
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
config := &AppConfig{
|
|
GitHubToken: tt.token,
|
|
Quiet: true,
|
|
}
|
|
generator := NewGenerator(config)
|
|
|
|
analyzer, err := generator.CreateDependencyAnalyzer()
|
|
|
|
if tt.expectError {
|
|
testutil.AssertError(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
testutil.AssertNoError(t, err)
|
|
|
|
if analyzer == nil {
|
|
t.Error("expected analyzer to be created")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerator_WithDifferentThemes(t *testing.T) {
|
|
t.Parallel()
|
|
themes := []string{"default", "github", "gitlab", "minimal", "professional"}
|
|
|
|
for _, theme := range themes {
|
|
t.Run("theme_"+theme, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Create separate temp directory for each theme test
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
// Set up test templates for this theme test
|
|
testutil.SetupTestTemplates(t, tmpDir)
|
|
|
|
actionPath := filepath.Join(tmpDir, appconstants.ActionFileNameYML)
|
|
testutil.WriteTestFile(t, actionPath, testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple))
|
|
|
|
config := &AppConfig{
|
|
Theme: theme,
|
|
OutputFormat: "md",
|
|
OutputDir: tmpDir,
|
|
Quiet: true,
|
|
}
|
|
generator := NewGenerator(config)
|
|
|
|
if err := generator.GenerateFromFile(actionPath); err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Verify output was created
|
|
readmeFiles, _ := filepath.Glob(filepath.Join(tmpDir, "README*.md"))
|
|
if len(readmeFiles) == 0 {
|
|
t.Errorf("no output file was created for theme %s", theme)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerator_ErrorHandling(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
setupFunc func(t *testing.T, tmpDir string) (*Generator, string)
|
|
wantError string
|
|
}{
|
|
{
|
|
name: "invalid template path",
|
|
setupFunc: func(t *testing.T, tmpDir string) (*Generator, string) {
|
|
t.Helper()
|
|
config := &AppConfig{
|
|
Template: "/nonexistent/template.tmpl",
|
|
OutputFormat: "md",
|
|
OutputDir: tmpDir,
|
|
Quiet: true,
|
|
}
|
|
generator := NewGenerator(config)
|
|
actionPath := filepath.Join(tmpDir, appconstants.ActionFileNameYML)
|
|
testutil.WriteTestFile(
|
|
t,
|
|
actionPath,
|
|
testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
)
|
|
|
|
return generator, actionPath
|
|
},
|
|
wantError: "template",
|
|
},
|
|
{
|
|
name: "permission denied on output directory",
|
|
setupFunc: func(t *testing.T, tmpDir string) (*Generator, string) {
|
|
t.Helper()
|
|
// Set up test templates
|
|
testutil.SetupTestTemplates(t, tmpDir)
|
|
|
|
// Create a directory with no write permissions
|
|
restrictedDir := filepath.Join(tmpDir, "restricted")
|
|
_ = os.MkdirAll(restrictedDir, 0444) // #nosec G301 -- intentionally read-only for test
|
|
|
|
config := &AppConfig{
|
|
OutputFormat: "md",
|
|
OutputDir: restrictedDir,
|
|
Quiet: true,
|
|
Template: filepath.Join(tmpDir, "templates", "readme.tmpl"),
|
|
}
|
|
generator := NewGenerator(config)
|
|
actionPath := filepath.Join(tmpDir, appconstants.ActionFileNameYML)
|
|
testutil.WriteTestFile(
|
|
t,
|
|
actionPath,
|
|
testutil.MustReadFixture(appconstants.TestFixtureJavaScriptSimple),
|
|
)
|
|
|
|
return generator, actionPath
|
|
},
|
|
wantError: "permission denied",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir, cleanup := testutil.TempDir(t)
|
|
defer cleanup()
|
|
|
|
generator, actionPath := tt.setupFunc(t, tmpDir)
|
|
err := generator.GenerateFromFile(actionPath)
|
|
|
|
testutil.AssertError(t, err)
|
|
if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tt.wantError)) {
|
|
t.Errorf("expected error containing %q, got: %v", tt.wantError, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// createTestDirs is a helper that creates multiple directories within tmpDir for testing.
|
|
// Returns the full paths of all created directories.
|
|
func createTestDirs(t *testing.T, tmpDir string, names ...string) []string {
|
|
t.Helper()
|
|
dirs := make([]string, len(names))
|
|
for i, name := range names {
|
|
dirPath := filepath.Join(tmpDir, name)
|
|
if err := os.MkdirAll(dirPath, 0750); err != nil { // #nosec G301 -- test directory permissions
|
|
t.Fatalf("failed to create directory %s: %v", name, err)
|
|
}
|
|
dirs[i] = dirPath
|
|
}
|
|
|
|
return dirs
|
|
}
|