From 0c344c39a7c2db6946f96e4d7b16b654ad604235 Mon Sep 17 00:00:00 2001 From: swappy <59965507+rycerzes@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:23:41 +0000 Subject: [PATCH 1/8] fix: generate reports from YAML Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- action.yml | 169 +++++++++++++++++++++++++++++++++++++++++++---------- install.sh | 40 +++++++------ 2 files changed, 161 insertions(+), 48 deletions(-) diff --git a/action.yml b/action.yml index 4262137..f9f89d3 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,9 @@ -name: 'Keploy TestGPT' +name: "Keploy TestGPT" description: "TestGPT is a GitHub Action designed to execute Keploy test cases and generate detailed test reports." author: Sonichigo branding: - icon: 'aperture' - color: 'orange' + icon: "aperture" + color: "orange" inputs: working-directory: @@ -19,7 +19,7 @@ inputs: delay: description: Time to start application required: true - default: 10 + default: 10s container-name: description: Name of the container in case of "docker compose" command build-delay: @@ -32,34 +32,108 @@ runs: - name: Setup GITHUB_PATH for script run: | echo "${{ github.action_path }}" >> $GITHUB_PATH - echo "${{ inputs.working-directory }}" + echo "Working directory: ${{ inputs.working-directory }}" + echo "Keploy path: ${{ inputs.keploy-path }}" shell: bash - name: Grant permissions run: chmod +x ${GITHUB_ACTION_PATH}/install.sh shell: bash + - name: Install YAML tools + run: | + wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + chmod +x /usr/local/bin/yq + shell: bash - id: keploy-test-report - name: Run Script - run: | - ${GITHUB_ACTION_PATH}/install.sh > ${GITHUB_WORKSPACE}/${WORKDIR}/report.txt - cat ${GITHUB_WORKSPACE}/${WORKDIR}/report.txt + name: Run Keploy Tests and Process Results + run: | + # Run the Keploy tests + ${GITHUB_ACTION_PATH}/install.sh > ${GITHUB_WORKSPACE}/${WORKDIR}/console-output.txt - # Search for the complete testrun summary and extract total tests, total test passed, and total test failed data - grep -oE "COMPLETE TESTRUN SUMMARY\.\s+Total tests: [0-9]+" ${GITHUB_WORKSPACE}/${WORKDIR}/report.txt | sed -r "s/\x1B\[[0-9;]*[mGK]//g" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out - grep -oE "COMPLETE TESTRUN SUMMARY\.\s+Total test passed: [0-9]+" ${GITHUB_WORKSPACE}/${WORKDIR}/report.txt | sed -r "s/\x1B\[[0-9;]*[mGK]//g" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out - grep -oE "COMPLETE TESTRUN SUMMARY\.\s+Total test failed: [0-9]+" ${GITHUB_WORKSPACE}/${WORKDIR}/report.txt | sed -r "s/\x1B\[[0-9;]*[mGK]//g" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out + # Look for all generated YAML reports + REPORT_DIR="${GITHUB_WORKSPACE}/${WORKDIR}/keploy/reports/test-run-0" - # Combine the results into a single file and prepare output - cat ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out > ${GITHUB_WORKSPACE}/${WORKDIR}/final.out - echo 'KEPLOY_REPORT< $GITHUB_OUTPUT - cat ${GITHUB_WORKSPACE}/${WORKDIR}/final.out >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - cat $GITHUB_OUTPUT + if [ -d "$REPORT_DIR" ]; then + echo "Found Keploy test reports directory at: $REPORT_DIR" + + # Initialize counters + TOTAL_TESTS=0 + PASSED_TESTS=0 + FAILED_TESTS=0 + STATUS="UNKNOWN" + + # Find all test report files + REPORT_FILES=$(find "$REPORT_DIR" -name "test-set-*-report.yaml" | sort) + + if [ -z "$REPORT_FILES" ]; then + echo "Error: No Keploy test reports found in directory: $REPORT_DIR" >&2 + exit 1 + fi + + echo "Processing test reports:" + + # Process each report file + for REPORT_PATH in $REPORT_FILES; do + echo "Processing report: $REPORT_PATH" + + # Validate YAML first + if ! yq eval '.' "$REPORT_PATH" > /dev/null 2>&1; then + echo "Error: Invalid YAML in report file: $REPORT_PATH" >&2 + continue + fi + + # Extract test results from YAML report + FILE_TOTAL=$(yq eval '.total // 0' "$REPORT_PATH") + FILE_PASSED=$(yq eval '.success // 0' "$REPORT_PATH") + FILE_FAILED=$(yq eval '.failure // 0' "$REPORT_PATH") + FILE_STATUS=$(yq eval '.status // "UNKNOWN"' "$REPORT_PATH") + + echo " Total: $FILE_TOTAL, Passed: $FILE_PASSED, Failed: $FILE_FAILED, Status: $FILE_STATUS" + + # Aggregate results + TOTAL_TESTS=$((TOTAL_TESTS + FILE_TOTAL)) + PASSED_TESTS=$((PASSED_TESTS + FILE_PASSED)) + FAILED_TESTS=$((FAILED_TESTS + FILE_FAILED)) + + # Update overall status (if any test set failed, overall status is FAILED) + if [ "$FILE_STATUS" = "FAILED" ]; then + STATUS="FAILED" + elif [ "$STATUS" != "FAILED" ] && [ "$FILE_STATUS" = "PASSED" ]; then + STATUS="PASSED" + fi + done + + # Create formatted output + echo "COMPLETE TESTRUN SUMMARY. Total tests: $TOTAL_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out + echo "COMPLETE TESTRUN SUMMARY. Total test passed: $PASSED_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out + echo "COMPLETE TESTRUN SUMMARY. Total test failed: $FAILED_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out + + # Combine the results into final.out + cat ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out > ${GITHUB_WORKSPACE}/${WORKDIR}/final.out + + # Set the output for GitHub Actions + { + echo 'KEPLOY_REPORT<> $GITHUB_OUTPUT + else + echo "Error: Keploy test reports directory not found at expected path: $REPORT_DIR" >&2 + echo "Contents of keploy directory:" + find ${GITHUB_WORKSPACE}/${WORKDIR}/keploy -type f | sort + exit 1 + fi shell: bash env: WORKDIR: ${{ inputs.working-directory }} DELAY: ${{ inputs.delay }} - COMMAND : ${{ inputs.command }} - KEPLOY_PATH: ${{inputs.keploy-path}} + COMMAND: ${{ inputs.command }} + KEPLOY_PATH: ${{ inputs.keploy-path }} + CONTAINER_NAME: ${{ inputs.container-name }} + BUILD_DELAY: ${{ inputs.build-delay }} - name: Check if report is generated run: | if [ -s ${GITHUB_WORKSPACE}/${WORKDIR}/final.out ]; then @@ -69,21 +143,54 @@ runs: exit 1 fi shell: bash + env: + WORKDIR: ${{ inputs.working-directory }} - name: Comment on PR - if: success() + if: success() && github.event_name == 'pull_request' uses: actions/github-script@v6 env: KEPLOY_REPORT: ${{ steps.keploy-test-report.outputs.KEPLOY_REPORT }} with: github-token: ${{ github.token }} script: | - if (!process.env.KEPLOY_REPORT) { - console.error('Error: KEPLOY_REPORT not found.'); - process.exit(1); + try { + if (!process.env.KEPLOY_REPORT) { + console.error('Error: KEPLOY_REPORT not found.'); + process.exit(1); + } + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: process.env.KEPLOY_REPORT + }); + console.log('Successfully commented on PR'); + } catch (error) { + console.error('Error commenting on PR:', error.message); + console.log('Report content (for reference):'); + console.log(process.env.KEPLOY_REPORT); + + // Don't fail the workflow if commenting fails + if (error.status === 403) { + console.log('Permission issue: Make sure your workflow has proper permissions.'); + console.log('Add the following to your workflow file:'); + console.log('permissions:'); + console.log(' contents: read'); + console.log(' issues: write'); + console.log(' pull-requests: write'); + } } - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: process.env.KEPLOY_REPORT - }) + + - name: Display Test Results + if: always() + run: | + echo "============ KEPLOY TEST RESULTS ============" + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" ]; then + cat "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" + else + echo "No test results found" + fi + shell: bash + env: + WORKDIR: ${{ inputs.working-directory }} diff --git a/install.sh b/install.sh index f998ddf..0813c56 100644 --- a/install.sh +++ b/install.sh @@ -8,53 +8,59 @@ chmod +x /usr/local/bin/keploy echo "Keploy installed successfully 🎉" cd ${GITHUB_WORKSPACE}/${WORKDIR} -echo "${GITHUB_WORKSPACE}/${WORKDIR}" +echo "Working directory: ${GITHUB_WORKSPACE}/${WORKDIR}" +echo "Keploy path: ${KEPLOY_PATH}" # Generate app binary -echo "ls" -ls +echo "Directory contents:" +ls -la + +# Debug: Check if test sets exist +echo "Checking for test-sets in ${KEPLOY_PATH}" +find ${KEPLOY_PATH} -type d -name "test-sets" -o -name "test-set-*" if [[ "$COMMAND" =~ .*"go".* ]]; then echo "go is present." go mod download go build -o application echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "./application" --delay ${DELAY} --path "${KEPLOY_PATH}" - sudo -E keploy test -c "./application" --delay ${DELAY} --path "${KEPLOY_PATH}" + echo "Running: sudo -E keploy test -c \"./application\" --delay ${DELAY} --path ${KEPLOY_PATH}" + sudo -E keploy test -c "./application" --delay ${DELAY} --path ${KEPLOY_PATH} + echo $? elif [[ "$COMMAND" =~ .*"node".* ]]; then echo "Node is present." npm install echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" + echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" + sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} -elif [[ "$COMMAND" =~ .*"java".* ]] || [[ "$COMMAND" =~ .*"mvn".* ]]; then +elif [[ "$COMMAND" =~ .*"java".* ]] || [[ "$COMMAND" =~ .*"mvn".* ]]; then echo "Java is present." mvn clean install echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" + echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" + sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} elif [[ "$COMMAND" =~ .*"python".* ]] || [[ "$COMMAND" =~ .*"python3".* ]]; then echo "Python is present." pip install -r requirements.txt echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" + echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" + sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} elif [[ "$COMMAND" =~ .*"docker-compose".* ]] || [[ "$COMMAND" =~ .*"docker compose".* ]]; then echo "Docker compose is present." echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" --containerName "${CONTAINER_NAME}" --buildDelay ${BUILD_DELAY} - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" --containerName "${CONTAINER_NAME}" --buildDelay ${BUILD_DELAY} + echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH} --containerName ${CONTAINER_NAME} --buildDelay ${BUILD_DELAY}" + sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} --containerName "${CONTAINER_NAME}" --buildDelay ${BUILD_DELAY} elif [[ "$COMMAND" =~ .*"docker".* ]]; then echo "Docker is present." echo 'Test Mode Starting 🎉' - echo sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" --buildDelay ${BUILD_DELAY} - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path "${KEPLOY_PATH}" --buildDelay ${BUILD_DELAY} + echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH} --buildDelay ${BUILD_DELAY}" + sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} --buildDelay ${BUILD_DELAY} else echo "Language not found" echo 'Test Mode Shutting 🎉' -fi \ No newline at end of file +fi From ecacacb1a4872b8d444fb59ac2c1f47ec037d71b Mon Sep 17 00:00:00 2001 From: swappy <59965507+rycerzes@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:21:13 +0000 Subject: [PATCH 2/8] refactor: use golang instead of shell scripts fix: whitespace syntax Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- action.yml | 34 ++++++--- go.mod | 3 + main.go | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 go.mod create mode 100644 main.go diff --git a/action.yml b/action.yml index f9f89d3..07009e9 100644 --- a/action.yml +++ b/action.yml @@ -35,20 +35,36 @@ runs: echo "Working directory: ${{ inputs.working-directory }}" echo "Keploy path: ${{ inputs.keploy-path }}" shell: bash - - name: Grant permissions - run: chmod +x ${GITHUB_ACTION_PATH}/install.sh + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Build and run Go implementation + run: | + cd ${GITHUB_ACTION_PATH} + go build -o keploy-runner main.go + chmod +x keploy-runner + ./keploy-runner > ${GITHUB_WORKSPACE}/${WORKDIR}/console-output.txt shell: bash + env: + WORKDIR: ${{ inputs.working-directory }} + DELAY: ${{ inputs.delay }} + COMMAND: ${{ inputs.command }} + KEPLOY_PATH: ${{ inputs.keploy-path }} + CONTAINER_NAME: ${{ inputs.container-name }} + BUILD_DELAY: ${{ inputs.build-delay }} + - name: Install YAML tools run: | wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 chmod +x /usr/local/bin/yq shell: bash + - id: keploy-test-report - name: Run Keploy Tests and Process Results + name: Process Test Results run: | - # Run the Keploy tests - ${GITHUB_ACTION_PATH}/install.sh > ${GITHUB_WORKSPACE}/${WORKDIR}/console-output.txt - # Look for all generated YAML reports REPORT_DIR="${GITHUB_WORKSPACE}/${WORKDIR}/keploy/reports/test-run-0" @@ -129,11 +145,7 @@ runs: shell: bash env: WORKDIR: ${{ inputs.working-directory }} - DELAY: ${{ inputs.delay }} - COMMAND: ${{ inputs.command }} - KEPLOY_PATH: ${{ inputs.keploy-path }} - CONTAINER_NAME: ${{ inputs.container-name }} - BUILD_DELAY: ${{ inputs.build-delay }} + - name: Check if report is generated run: | if [ -s ${GITHUB_WORKSPACE}/${WORKDIR}/final.out ]; then diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bef103f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module testGPT + +go 1.23.2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..9a2e8d2 --- /dev/null +++ b/main.go @@ -0,0 +1,210 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + // Get environment variables + githubWorkspace := os.Getenv("GITHUB_WORKSPACE") + workDir := os.Getenv("WORKDIR") + keployPath := os.Getenv("KEPLOY_PATH") + delay := os.Getenv("DELAY") + command := os.Getenv("COMMAND") + containerName := os.Getenv("CONTAINER_NAME") + buildDelay := os.Getenv("BUILD_DELAY") + + workingDir := filepath.Join(githubWorkspace, workDir) + + // Install Keploy + if err := installKeploy(); err != nil { + log.Fatalf("Failed to install Keploy: %v", err) + } + + if err := os.Chdir(workingDir); err != nil { + log.Fatalf("Failed to change to working directory %s: %v", workingDir, err) + } + fmt.Printf("Working directory: %s\n", workingDir) + fmt.Printf("Keploy path: %s\n", keployPath) + + // Debug: List directory contents + fmt.Println("Directory contents:") + dirEntries, err := os.ReadDir(".") + if err != nil { + log.Fatalf("Failed to read directory: %v", err) + } + for _, entry := range dirEntries { + info, _ := entry.Info() + var sizeStr string + if info != nil { + sizeStr = fmt.Sprintf("%d", info.Size()) + } else { + sizeStr = "-" + } + fmt.Printf("%s %s\n", entry.Name(), sizeStr) + } + + // Debug: Check for test sets + fmt.Printf("Checking for test-sets in %s\n", keployPath) + checkTestSets(keployPath) + + // Execute based on the command type + if strings.Contains(command, "go") { + fmt.Println("go is present.") + + runCommand("go", "mod", "download") + runCommand("go", "build", "-o", "application") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"./application\" --delay %s --path %s\n", delay, keployPath) + + runKeployTest("./application", delay, keployPath, "", "") + + } else if strings.Contains(command, "node") { + fmt.Println("Node is present.") + + runCommand("npm", "install") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"%s\" --delay %s --path %s\n", command, delay, keployPath) + + runKeployTest(command, delay, keployPath, "", "") + + } else if strings.Contains(command, "java") || strings.Contains(command, "mvn") { + fmt.Println("Java is present.") + + runCommand("mvn", "clean", "install") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"%s\" --delay %s --path %s\n", command, delay, keployPath) + + runKeployTest(command, delay, keployPath, "", "") + + } else if strings.Contains(command, "python") || strings.Contains(command, "python3") { + fmt.Println("Python is present.") + + runCommand("pip", "install", "-r", "requirements.txt") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"%s\" --delay %s --path %s\n", command, delay, keployPath) + + runKeployTest(command, delay, keployPath, "", "") + + } else if strings.Contains(command, "docker-compose") || strings.Contains(command, "docker compose") { + fmt.Println("Docker compose is present.") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"%s\" --delay %s --path %s --containerName %s --buildDelay %s\n", + command, delay, keployPath, containerName, buildDelay) + + runKeployTest(command, delay, keployPath, containerName, buildDelay) + + } else if strings.Contains(command, "docker") { + fmt.Println("Docker is present.") + + fmt.Println("Test Mode Starting 🎉") + fmt.Printf("Running: sudo -E keploy test -c \"%s\" --delay %s --path %s --buildDelay %s\n", + command, delay, keployPath, buildDelay) + + runKeployTest(command, delay, keployPath, "", buildDelay) + + } else { + fmt.Println("Language not found") + fmt.Println("Test Mode Shutting 🎉") + } +} + +func installKeploy() error { + fmt.Println("Installing Keploy...") + + // Download the Keploy binary + resp, err := http.Get("https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz") + if err != nil { + return fmt.Errorf("failed to download Keploy: %v", err) + } + defer resp.Body.Close() + + // Create a temporary file to save the tarball + tarFile, err := os.CreateTemp("", "keploy_*.tar.gz") + if err != nil { + return fmt.Errorf("failed to create temp file: %v", err) + } + defer os.Remove(tarFile.Name()) + + // Save the tarball to the temporary file + _, err = io.Copy(tarFile, resp.Body) + if err != nil { + return fmt.Errorf("failed to save tarball: %v", err) + } + tarFile.Close() + + // Extract the tarball + cmd := exec.Command("tar", "xz", "-C", "/tmp", "-f", tarFile.Name()) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to extract tarball: %v", err) + } + + // Move keploy to /usr/local/bin + cmd = exec.Command("sudo", "mv", "/tmp/keploy", "/usr/local/bin/keploy") + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to move keploy to /usr/local/bin: %v", err) + } + + // Make keploy executable + cmd = exec.Command("sudo", "chmod", "+x", "/usr/local/bin/keploy") + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to make keploy executable: %v", err) + } + + fmt.Println("Keploy installed successfully 🎉") + return nil +} + +func checkTestSets(keployPath string) { + cmd := exec.Command("find", keployPath, "-type", "d", "-name", "test-sets", "-o", "-name", "test-set-*") + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("Error checking for test sets: %v\n", err) + return + } + fmt.Println(string(output)) +} + +func runCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func runKeployTest(command, delay, keployPath, containerName, buildDelay string) { + args := []string{"-E", "keploy", "test", "-c", command, "--delay", delay, "--path", keployPath} + + if containerName != "" { + args = append(args, "--containerName", containerName) + } + + if buildDelay != "" { + args = append(args, "--buildDelay", buildDelay) + } + + cmd := exec.Command("sudo", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + fmt.Printf("Error running Keploy test: %v\n", err) + if exitErr, ok := err.(*exec.ExitError); ok { + fmt.Printf("Exit code: %d\n", exitErr.ExitCode()) + } + } else { + fmt.Println("Keploy test completed successfully") + } +} From 77fb370acaa282f983fe2a1df2ff37fa721d72a8 Mon Sep 17 00:00:00 2001 From: swappy <59965507+rycerzes@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:49:10 +0000 Subject: [PATCH 3/8] feat: go implementation of YAML parsing and report gen Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> fix: golang version Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- action.yml | 92 +++--------------------------- go.mod | 2 + go.sum | 4 ++ install.sh | 66 ---------------------- main.go | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 171 insertions(+), 156 deletions(-) create mode 100644 go.sum delete mode 100644 install.sh diff --git a/action.yml b/action.yml index 07009e9..bd2f276 100644 --- a/action.yml +++ b/action.yml @@ -37,13 +37,15 @@ runs: shell: bash - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.23.2' - name: Build and run Go implementation run: | cd ${GITHUB_ACTION_PATH} + # Install required Go packages + go get gopkg.in/yaml.v3 go build -o keploy-runner main.go chmod +x keploy-runner ./keploy-runner > ${GITHUB_WORKSPACE}/${WORKDIR}/console-output.txt @@ -56,90 +58,13 @@ runs: CONTAINER_NAME: ${{ inputs.container-name }} BUILD_DELAY: ${{ inputs.build-delay }} - - name: Install YAML tools - run: | - wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 - chmod +x /usr/local/bin/yq - shell: bash - - id: keploy-test-report name: Process Test Results run: | - # Look for all generated YAML reports - REPORT_DIR="${GITHUB_WORKSPACE}/${WORKDIR}/keploy/reports/test-run-0" - - if [ -d "$REPORT_DIR" ]; then - echo "Found Keploy test reports directory at: $REPORT_DIR" - - # Initialize counters - TOTAL_TESTS=0 - PASSED_TESTS=0 - FAILED_TESTS=0 - STATUS="UNKNOWN" - - # Find all test report files - REPORT_FILES=$(find "$REPORT_DIR" -name "test-set-*-report.yaml" | sort) - - if [ -z "$REPORT_FILES" ]; then - echo "Error: No Keploy test reports found in directory: $REPORT_DIR" >&2 - exit 1 - fi - - echo "Processing test reports:" - - # Process each report file - for REPORT_PATH in $REPORT_FILES; do - echo "Processing report: $REPORT_PATH" - - # Validate YAML first - if ! yq eval '.' "$REPORT_PATH" > /dev/null 2>&1; then - echo "Error: Invalid YAML in report file: $REPORT_PATH" >&2 - continue - fi - - # Extract test results from YAML report - FILE_TOTAL=$(yq eval '.total // 0' "$REPORT_PATH") - FILE_PASSED=$(yq eval '.success // 0' "$REPORT_PATH") - FILE_FAILED=$(yq eval '.failure // 0' "$REPORT_PATH") - FILE_STATUS=$(yq eval '.status // "UNKNOWN"' "$REPORT_PATH") - - echo " Total: $FILE_TOTAL, Passed: $FILE_PASSED, Failed: $FILE_FAILED, Status: $FILE_STATUS" - - # Aggregate results - TOTAL_TESTS=$((TOTAL_TESTS + FILE_TOTAL)) - PASSED_TESTS=$((PASSED_TESTS + FILE_PASSED)) - FAILED_TESTS=$((FAILED_TESTS + FILE_FAILED)) - - # Update overall status (if any test set failed, overall status is FAILED) - if [ "$FILE_STATUS" = "FAILED" ]; then - STATUS="FAILED" - elif [ "$STATUS" != "FAILED" ] && [ "$FILE_STATUS" = "PASSED" ]; then - STATUS="PASSED" - fi - done - - # Create formatted output - echo "COMPLETE TESTRUN SUMMARY. Total tests: $TOTAL_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out - echo "COMPLETE TESTRUN SUMMARY. Total test passed: $PASSED_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out - echo "COMPLETE TESTRUN SUMMARY. Total test failed: $FAILED_TESTS" > ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out - - # Combine the results into final.out - cat ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out ${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out > ${GITHUB_WORKSPACE}/${WORKDIR}/final.out - - # Set the output for GitHub Actions - { - echo 'KEPLOY_REPORT<> $GITHUB_OUTPUT + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/github_output.txt" ]; then + cat "${GITHUB_WORKSPACE}/${WORKDIR}/github_output.txt" >> $GITHUB_OUTPUT else - echo "Error: Keploy test reports directory not found at expected path: $REPORT_DIR" >&2 - echo "Contents of keploy directory:" - find ${GITHUB_WORKSPACE}/${WORKDIR}/keploy -type f | sort + echo "Error: GitHub output file not found at: ${GITHUB_WORKSPACE}/${WORKDIR}/github_output.txt" >&2 exit 1 fi shell: bash @@ -148,7 +73,7 @@ runs: - name: Check if report is generated run: | - if [ -s ${GITHUB_WORKSPACE}/${WORKDIR}/final.out ]; then + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" ]; then echo "Report generated successfully." else echo "Error: Report not generated." >&2 @@ -157,6 +82,7 @@ runs: shell: bash env: WORKDIR: ${{ inputs.working-directory }} + - name: Comment on PR if: success() && github.event_name == 'pull_request' uses: actions/github-script@v6 diff --git a/go.mod b/go.mod index bef103f..393da08 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module testGPT go 1.23.2 + +require gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a62c313 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/install.sh b/install.sh deleted file mode 100644 index 0813c56..0000000 --- a/install.sh +++ /dev/null @@ -1,66 +0,0 @@ -# Install Keploy binary using curl command -curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp -echo "curl --silent --location 'https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz' | tar xz -C /tmp" - -sudo mv /tmp/keploy /usr/local/bin/keploy -chmod +x /usr/local/bin/keploy - -echo "Keploy installed successfully 🎉" - -cd ${GITHUB_WORKSPACE}/${WORKDIR} -echo "Working directory: ${GITHUB_WORKSPACE}/${WORKDIR}" -echo "Keploy path: ${KEPLOY_PATH}" -# Generate app binary -echo "Directory contents:" -ls -la - -# Debug: Check if test sets exist -echo "Checking for test-sets in ${KEPLOY_PATH}" -find ${KEPLOY_PATH} -type d -name "test-sets" -o -name "test-set-*" - -if [[ "$COMMAND" =~ .*"go".* ]]; then - echo "go is present." - go mod download - go build -o application - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"./application\" --delay ${DELAY} --path ${KEPLOY_PATH}" - sudo -E keploy test -c "./application" --delay ${DELAY} --path ${KEPLOY_PATH} - echo $? - -elif [[ "$COMMAND" =~ .*"node".* ]]; then - echo "Node is present." - npm install - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} - -elif [[ "$COMMAND" =~ .*"java".* ]] || [[ "$COMMAND" =~ .*"mvn".* ]]; then - echo "Java is present." - mvn clean install - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} - -elif [[ "$COMMAND" =~ .*"python".* ]] || [[ "$COMMAND" =~ .*"python3".* ]]; then - echo "Python is present." - pip install -r requirements.txt - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} - -elif [[ "$COMMAND" =~ .*"docker-compose".* ]] || [[ "$COMMAND" =~ .*"docker compose".* ]]; then - echo "Docker compose is present." - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH} --containerName ${CONTAINER_NAME} --buildDelay ${BUILD_DELAY}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} --containerName "${CONTAINER_NAME}" --buildDelay ${BUILD_DELAY} - -elif [[ "$COMMAND" =~ .*"docker".* ]]; then - echo "Docker is present." - echo 'Test Mode Starting 🎉' - echo "Running: sudo -E keploy test -c \"${COMMAND}\" --delay ${DELAY} --path ${KEPLOY_PATH} --buildDelay ${BUILD_DELAY}" - sudo -E keploy test -c "${COMMAND}" --delay ${DELAY} --path ${KEPLOY_PATH} --buildDelay ${BUILD_DELAY} - -else - echo "Language not found" - echo 'Test Mode Shutting 🎉' -fi diff --git a/main.go b/main.go index 9a2e8d2..7e1105c 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io" "log" @@ -9,8 +10,26 @@ import ( "os/exec" "path/filepath" "strings" + + "gopkg.in/yaml.v3" ) +// TestReport represents the structure of a Keploy test report +type TestReport struct { + Total int `yaml:"total"` + Success int `yaml:"success"` + Failure int `yaml:"failure"` + Status string `yaml:"status"` +} + +// AggregatedReport holds the final aggregated test results +type AggregatedReport struct { + TotalTests int `json:"total_tests"` + PassedTests int `json:"passed_tests"` + FailedTests int `json:"failed_tests"` + Status string `json:"status"` +} + func main() { // Get environment variables githubWorkspace := os.Getenv("GITHUB_WORKSPACE") @@ -23,7 +42,6 @@ func main() { workingDir := filepath.Join(githubWorkspace, workDir) - // Install Keploy if err := installKeploy(); err != nil { log.Fatalf("Failed to install Keploy: %v", err) } @@ -55,7 +73,6 @@ func main() { fmt.Printf("Checking for test-sets in %s\n", keployPath) checkTestSets(keployPath) - // Execute based on the command type if strings.Contains(command, "go") { fmt.Println("go is present.") @@ -119,6 +136,8 @@ func main() { fmt.Println("Language not found") fmt.Println("Test Mode Shutting 🎉") } + + processTestReports(githubWorkspace, workDir) } func installKeploy() error { @@ -131,33 +150,28 @@ func installKeploy() error { } defer resp.Body.Close() - // Create a temporary file to save the tarball tarFile, err := os.CreateTemp("", "keploy_*.tar.gz") if err != nil { return fmt.Errorf("failed to create temp file: %v", err) } defer os.Remove(tarFile.Name()) - // Save the tarball to the temporary file _, err = io.Copy(tarFile, resp.Body) if err != nil { return fmt.Errorf("failed to save tarball: %v", err) } tarFile.Close() - // Extract the tarball cmd := exec.Command("tar", "xz", "-C", "/tmp", "-f", tarFile.Name()) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to extract tarball: %v", err) } - // Move keploy to /usr/local/bin cmd = exec.Command("sudo", "mv", "/tmp/keploy", "/usr/local/bin/keploy") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to move keploy to /usr/local/bin: %v", err) } - // Make keploy executable cmd = exec.Command("sudo", "chmod", "+x", "/usr/local/bin/keploy") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to make keploy executable: %v", err) @@ -208,3 +222,138 @@ func runKeployTest(command, delay, keployPath, containerName, buildDelay string) fmt.Println("Keploy test completed successfully") } } + +func processTestReports(githubWorkspace, workDir string) { + reportDir := filepath.Join(githubWorkspace, workDir, "keploy/reports/test-run-0") + + fmt.Printf("Looking for test reports in: %s\n", reportDir) + + if _, err := os.Stat(reportDir); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Error: Keploy test reports directory not found at: %s\n", reportDir) + listKeployFiles(filepath.Join(githubWorkspace, workDir, "keploy")) + os.Exit(1) + } + + var reportFiles []string + err := filepath.Walk(reportDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && strings.HasPrefix(filepath.Base(path), "test-set-") && + strings.HasSuffix(filepath.Base(path), "-report.yaml") { + reportFiles = append(reportFiles, path) + } + return nil + }) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error walking through report directory: %v\n", err) + os.Exit(1) + } + + if len(reportFiles) == 0 { + fmt.Fprintf(os.Stderr, "Error: No Keploy test reports found in directory: %s\n", reportDir) + os.Exit(1) + } + + totalTests := 0 + passedTests := 0 + failedTests := 0 + status := "UNKNOWN" + + fmt.Println("Processing test reports:") + + for _, reportPath := range reportFiles { + fmt.Printf("Processing report: %s\n", reportPath) + + data, err := os.ReadFile(reportPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading file %s: %v\n", reportPath, err) + continue + } + + var report TestReport + if err := yaml.Unmarshal(data, &report); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing YAML in file %s: %v\n", reportPath, err) + continue + } + + fmt.Printf(" Total: %d, Passed: %d, Failed: %d, Status: %s\n", + report.Total, report.Success, report.Failure, report.Status) + + totalTests += report.Total + passedTests += report.Success + failedTests += report.Failure + + if report.Status == "FAILED" { + status = "FAILED" + } else if status != "FAILED" && report.Status == "PASSED" { + status = "PASSED" + } + } + + outputDir := filepath.Join(githubWorkspace, workDir) + if _, err := os.Stat(outputDir); os.IsNotExist(err) { + os.MkdirAll(outputDir, 0755) + } + + os.WriteFile( + filepath.Join(outputDir, "final_total_tests.out"), + []byte(fmt.Sprintf("COMPLETE TESTRUN SUMMARY. Total tests: %d\n", totalTests)), + 0644, + ) + os.WriteFile( + filepath.Join(outputDir, "final_total_passed.out"), + []byte(fmt.Sprintf("COMPLETE TESTRUN SUMMARY. Total test passed: %d\n", passedTests)), + 0644, + ) + os.WriteFile( + filepath.Join(outputDir, "final_total_failed.out"), + []byte(fmt.Sprintf("COMPLETE TESTRUN SUMMARY. Total test failed: %d\n", failedTests)), + 0644, + ) + + finalOutput := fmt.Sprintf( + "COMPLETE TESTRUN SUMMARY. Total tests: %d\n"+ + "COMPLETE TESTRUN SUMMARY. Total test passed: %d\n"+ + "COMPLETE TESTRUN SUMMARY. Total test failed: %d\n", + totalTests, passedTests, failedTests, + ) + os.WriteFile(filepath.Join(outputDir, "final.out"), []byte(finalOutput), 0644) + + aggregatedReport := AggregatedReport{ + TotalTests: totalTests, + PassedTests: passedTests, + FailedTests: failedTests, + Status: status, + } + + jsonData, err := json.MarshalIndent(aggregatedReport, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "Error generating JSON report: %v\n", err) + os.Exit(1) + } + + os.WriteFile(filepath.Join(outputDir, "keploy_report.json"), jsonData, 0644) + fmt.Println("Test report processing complete") + + githubOutput := fmt.Sprintf( + "KEPLOY_REPORT< Date: Tue, 1 Apr 2025 12:09:18 +0000 Subject: [PATCH 4/8] fix: summary format fix: text stylization Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- action.yml | 11 +++++++++ main.go | 69 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/action.yml b/action.yml index bd2f276..b4c94e6 100644 --- a/action.yml +++ b/action.yml @@ -126,6 +126,17 @@ runs: echo "============ KEPLOY TEST RESULTS ============" if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" ]; then cat "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" + echo "" + echo "============ SUMMARY ============" + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out" ]; then + cat "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_tests.out" + fi + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out" ]; then + cat "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_passed.out" + fi + if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out" ]; then + cat "${GITHUB_WORKSPACE}/${WORKDIR}/final_total_failed.out" + fi else echo "No test results found" fi diff --git a/main.go b/main.go index 7e1105c..b64d786 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,13 @@ type TestReport struct { Status string `yaml:"status"` } +// TestSetReport represents details for a single test set +type TestSetReport struct { + ID string + PassedTests int + FailedTests int +} + // AggregatedReport holds the final aggregated test results type AggregatedReport struct { TotalTests int `json:"total_tests"` @@ -260,6 +267,7 @@ func processTestReports(githubWorkspace, workDir string) { passedTests := 0 failedTests := 0 status := "UNKNOWN" + var testSets []TestSetReport fmt.Println("Processing test reports:") @@ -278,8 +286,19 @@ func processTestReports(githubWorkspace, workDir string) { continue } - fmt.Printf(" Total: %d, Passed: %d, Failed: %d, Status: %s\n", - report.Total, report.Success, report.Failure, report.Status) + // Extract the test set ID from the filename + base := filepath.Base(reportPath) + testSetID := strings.TrimSuffix(base, "-report.yaml") + + testSetReport := TestSetReport{ + ID: testSetID, + PassedTests: report.Success, + FailedTests: report.Failure, + } + testSets = append(testSets, testSetReport) + + fmt.Printf(" %s: Passed: %d, Failed: %d\n", + testSetID, report.Success, report.Failure) totalTests += report.Total passedTests += report.Success @@ -297,6 +316,16 @@ func processTestReports(githubWorkspace, workDir string) { os.MkdirAll(outputDir, 0755) } + var detailedReport strings.Builder + detailedReport.WriteString("testrun summary\n") + for _, testSet := range testSets { + detailedReport.WriteString(fmt.Sprintf("id: %s\n", testSet.ID)) + detailedReport.WriteString(fmt.Sprintf("tests passed: %d\n", testSet.PassedTests)) + detailedReport.WriteString(fmt.Sprintf("test failed: %d\n\n", testSet.FailedTests)) + } + + detailedReportStr := detailedReport.String() + os.WriteFile( filepath.Join(outputDir, "final_total_tests.out"), []byte(fmt.Sprintf("COMPLETE TESTRUN SUMMARY. Total tests: %d\n", totalTests)), @@ -313,13 +342,7 @@ func processTestReports(githubWorkspace, workDir string) { 0644, ) - finalOutput := fmt.Sprintf( - "COMPLETE TESTRUN SUMMARY. Total tests: %d\n"+ - "COMPLETE TESTRUN SUMMARY. Total test passed: %d\n"+ - "COMPLETE TESTRUN SUMMARY. Total test failed: %d\n", - totalTests, passedTests, failedTests, - ) - os.WriteFile(filepath.Join(outputDir, "final.out"), []byte(finalOutput), 0644) + os.WriteFile(filepath.Join(outputDir, "final.out"), []byte(detailedReportStr), 0644) aggregatedReport := AggregatedReport{ TotalTests: totalTests, @@ -337,17 +360,23 @@ func processTestReports(githubWorkspace, workDir string) { os.WriteFile(filepath.Join(outputDir, "keploy_report.json"), jsonData, 0644) fmt.Println("Test report processing complete") - githubOutput := fmt.Sprintf( - "KEPLOY_REPORT< Date: Tue, 1 Apr 2025 12:39:37 +0000 Subject: [PATCH 5/8] feat: gh actions for binary fix: gh action issue Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> fix: release gh action Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- .github/workflows/release.yml | 76 ++++++++++++++++++++++++++++------- action.yml | 19 ++++----- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee135ac..b681f3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ on: push: - # Sequence of patterns matched against refs/tags - tags: - - 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10 + branches: + - main + - func name: Create Release @@ -10,20 +10,66 @@ jobs: build: name: Create Release runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code - uses: actions/checkout@v2 - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.2" + + - name: Install dependencies + run: go get gopkg.in/yaml.v3 + + - name: Build binary + run: go build -o keploy-runner main.go + + - name: Extract commit information + id: commit_info + run: | + COMMIT_MSG=$(git log -1 --pretty=%s) + COMMIT_BODY=$(git log -1 --pretty=%b) + # Get the latest tag if it exists + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "No previous tags") + + echo "message<> $GITHUB_OUTPUT + echo "$COMMIT_MSG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "body<> $GITHUB_OUTPUT + echo "$COMMIT_BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + - name: Create latest tag + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + # Create or update the 'latest' tag + git tag -fa latest -m "Latest release" + # Push the latest tag + git push origin latest --force + + - name: Create Latest Release + uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} + tag_name: latest + name: Latest Release body: | - Changes in this Release - - First Change - - Second Change + # Latest Release + + This is an automatically updated release that always points to the most recent build. + + Latest commit: ${{ steps.commit_info.outputs.message }} + + This release includes the pre-built binary for the TestGPT GitHub Action. draft: false - prerelease: false \ No newline at end of file + prerelease: false + files: | + keploy-runner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/action.yml b/action.yml index b4c94e6..beb601f 100644 --- a/action.yml +++ b/action.yml @@ -25,6 +25,9 @@ inputs: build-delay: description: Time to wait for docker container build default: 50s + runner-version: + description: Version of the runner to use + default: "latest" runs: using: "composite" @@ -36,18 +39,16 @@ runs: echo "Keploy path: ${{ inputs.keploy-path }}" shell: bash - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.23.2' + - name: Download pre-built runner + run: | + echo "Downloading from: https://github.com/rycerzes/testGPT/releases/download/latest/keploy-runner" + curl -L -o ${GITHUB_ACTION_PATH}/keploy-runner https://github.com/rycerzes/testGPT/releases/download/latest/keploy-runner + chmod +x ${GITHUB_ACTION_PATH}/keploy-runner + shell: bash - - name: Build and run Go implementation + - name: Run Keploy TestGPT run: | cd ${GITHUB_ACTION_PATH} - # Install required Go packages - go get gopkg.in/yaml.v3 - go build -o keploy-runner main.go - chmod +x keploy-runner ./keploy-runner > ${GITHUB_WORKSPACE}/${WORKDIR}/console-output.txt shell: bash env: From 118488f95bb51bde5967df2c368fa68215a681fe Mon Sep 17 00:00:00 2001 From: swappy <59965507+rycerzes@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:30:16 +0000 Subject: [PATCH 6/8] feat: PR Details shown fix: markdown stylization fix: non PR context Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- .gitignore | 25 ++++++++ action.yml | 4 ++ go.mod | 8 ++- go.sum | 10 +++ main.go | 34 ++++++++++ utils/pr_analysis.go | 146 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 utils/pr_analysis.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64ae7f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env \ No newline at end of file diff --git a/action.yml b/action.yml index beb601f..b878269 100644 --- a/action.yml +++ b/action.yml @@ -28,6 +28,9 @@ inputs: runner-version: description: Version of the runner to use default: "latest" + github-token: + description: GitHub token for API access to fetch PR details + default: ${{ github.token }} runs: using: "composite" @@ -58,6 +61,7 @@ runs: KEPLOY_PATH: ${{ inputs.keploy-path }} CONTAINER_NAME: ${{ inputs.container-name }} BUILD_DELAY: ${{ inputs.build-delay }} + GITHUB_TOKEN: ${{ inputs.github-token }} - id: keploy-test-report name: Process Test Results diff --git a/go.mod b/go.mod index 393da08..c10b79e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,10 @@ module testGPT go 1.23.2 -require gopkg.in/yaml.v3 v3.0.1 +require ( + github.com/google/go-github/v70 v70.0.0 + golang.org/x/oauth2 v0.28.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require github.com/google/go-querystring v1.1.0 // indirect diff --git a/go.sum b/go.sum index a62c313..0f3cccb 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,13 @@ +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= +github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go index b64d786..791f94b 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,8 @@ import ( "strings" "gopkg.in/yaml.v3" + + "testGPT/utils" ) // TestReport represents the structure of a Keploy test report @@ -311,11 +313,35 @@ func processTestReports(githubWorkspace, workDir string) { } } + // Get PR details if we're in a PR context + var prDetailsMarkdown string + isPRContext := false + prNumber, isPR := utils.GetPRNumberFromEnv() + if isPR { + isPRContext = true + fmt.Printf("Running in PR context. PR number: %d\n", prNumber) + client, err := utils.NewClient() + if err != nil { + fmt.Printf("Warning: Failed to create GitHub client: %v\n", err) + } else { + prDetails, err := client.GetPRDetails(prNumber) + if err != nil { + fmt.Printf("Warning: Failed to fetch PR details: %v\n", err) + } else { + prDetailsMarkdown = utils.FormatPRDetailsForComment(prDetails) + fmt.Printf("Successfully fetched details for PR #%d\n", prNumber) + } + } + } else { + fmt.Println("Not running in a PR context, running in manual trigger mode") + } + outputDir := filepath.Join(githubWorkspace, workDir) if _, err := os.Stat(outputDir); os.IsNotExist(err) { os.MkdirAll(outputDir, 0755) } + // Create a detailed report for all contexts var detailedReport strings.Builder detailedReport.WriteString("testrun summary\n") for _, testSet := range testSets { @@ -360,9 +386,17 @@ func processTestReports(githubWorkspace, workDir string) { os.WriteFile(filepath.Join(outputDir, "keploy_report.json"), jsonData, 0644) fmt.Println("Test report processing complete") + // Create GitHub output for both PR and non-PR contexts var githubOutputBuilder strings.Builder githubOutputBuilder.WriteString("KEPLOY_REPORT<\n") + sb.WriteString("**PR Details**\n\n") + sb.WriteString(fmt.Sprintf("**State**: %s\n", pr.State)) + sb.WriteString(fmt.Sprintf("**Author**: %s\n", pr.Author)) + sb.WriteString(fmt.Sprintf("**Created**: %s\n", pr.CreatedAt)) + sb.WriteString(fmt.Sprintf("**Updated**: %s\n", pr.UpdatedAt)) + sb.WriteString("\n\n") + + sb.WriteString("
\n") + sb.WriteString("**Changed Files**\n\n") + + for _, file := range pr.ChangedFiles { + sb.WriteString(fmt.Sprintf("##### %s (%s)\n", file.Filename, file.Status)) + sb.WriteString(fmt.Sprintf("##### Changes: +%d, -%d\n\n", file.Additions, file.Deletions)) + } + + sb.WriteString("
\n\n") + + return sb.String() +} + +func GetPRNumberFromEnv() (int, bool) { + ref := os.Getenv("GITHUB_REF") + if ref == "" || !strings.HasPrefix(ref, "refs/pull/") { + return 0, false + } + + parts := strings.Split(ref, "/") + if len(parts) < 3 { + return 0, false + } + + prNum, err := strconv.Atoi(parts[2]) + if err != nil { + return 0, false + } + + return prNum, true +} From 84365760eb7b8c2f72d44965877415857a032a2e Mon Sep 17 00:00:00 2001 From: swappy <59965507+rycerzes@users.noreply.github.com> Date: Tue, 1 Apr 2025 20:14:19 +0000 Subject: [PATCH 7/8] fix: markdown stylization Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- main.go | 22 +++++++++------------- utils/pr_analysis.go | 21 ++++++++++----------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/main.go b/main.go index 791f94b..cc02460 100644 --- a/main.go +++ b/main.go @@ -389,25 +389,21 @@ func processTestReports(githubWorkspace, workDir string) { // Create GitHub output for both PR and non-PR contexts var githubOutputBuilder strings.Builder githubOutputBuilder.WriteString("KEPLOY_REPORT<\n") + githubOutputBuilder.WriteString("**🔍 PR Analysis**\n\n") githubOutputBuilder.WriteString(prDetailsMarkdown) - githubOutputBuilder.WriteString("\n---\n\n") + githubOutputBuilder.WriteString("\n\n") } - githubOutputBuilder.WriteString("**Test Run Summary**\n\n") - - for _, testSet := range testSets { - githubOutputBuilder.WriteString(fmt.Sprintf("- **%s**\n", testSet.ID)) - githubOutputBuilder.WriteString(fmt.Sprintf(" - Tests passed: %d\n", testSet.PassedTests)) - githubOutputBuilder.WriteString(fmt.Sprintf(" - Tests failed: %d\n\n", testSet.FailedTests)) - } - - githubOutputBuilder.WriteString(fmt.Sprintf("**Total Tests:** %d\n", totalTests)) - githubOutputBuilder.WriteString(fmt.Sprintf("**Total Passed:** %d\n", passedTests)) - githubOutputBuilder.WriteString(fmt.Sprintf("**Total Failed:** %d\n", failedTests)) githubOutputBuilder.WriteString("EOF\n") os.WriteFile(filepath.Join(outputDir, "github_output.txt"), []byte(githubOutputBuilder.String()), 0644) diff --git a/utils/pr_analysis.go b/utils/pr_analysis.go index c462827..862d792 100644 --- a/utils/pr_analysis.go +++ b/utils/pr_analysis.go @@ -103,26 +103,25 @@ func (c *Client) GetPRDetails(prNumber int) (*PRDetails, error) { func FormatPRDetailsForComment(pr *PRDetails) string { var sb strings.Builder - sb.WriteString(fmt.Sprintf("**📊 PR #%d: %s**\n\n", pr.Number, pr.Title)) - - sb.WriteString("
\n") - sb.WriteString("**PR Details**\n\n") + sb.WriteString("### **Test Results Summary**\n\n") + sb.WriteString(fmt.Sprintf("**PR Number**: #%d\n", pr.Number)) + sb.WriteString(fmt.Sprintf("**Title**: %s\n", pr.Title)) sb.WriteString(fmt.Sprintf("**State**: %s\n", pr.State)) sb.WriteString(fmt.Sprintf("**Author**: %s\n", pr.Author)) - sb.WriteString(fmt.Sprintf("**Created**: %s\n", pr.CreatedAt)) - sb.WriteString(fmt.Sprintf("**Updated**: %s\n", pr.UpdatedAt)) - sb.WriteString("
\n\n") + sb.WriteString(fmt.Sprintf("**Created At**: %s\n", pr.CreatedAt)) + sb.WriteString(fmt.Sprintf("**Updated At**: %s\n\n", pr.UpdatedAt)) sb.WriteString("
\n") - sb.WriteString("**Changed Files**\n\n") + sb.WriteString("**🔍 PR Analysis Details**\n\n") + sb.WriteString("#### **Changed Files**\n\n") for _, file := range pr.ChangedFiles { - sb.WriteString(fmt.Sprintf("##### %s (%s)\n", file.Filename, file.Status)) - sb.WriteString(fmt.Sprintf("##### Changes: +%d, -%d\n\n", file.Additions, file.Deletions)) + sb.WriteString(fmt.Sprintf("- **File**: `%s` (%s)\n", file.Filename, file.Status)) + sb.WriteString(fmt.Sprintf(" - **Additions**: %d\n", file.Additions)) + sb.WriteString(fmt.Sprintf(" - **Deletions**: %d\n\n", file.Deletions)) } sb.WriteString("
\n\n") - return sb.String() } From 7af0b08e49f8db417a4a9fd973c5007bc3aefa97 Mon Sep 17 00:00:00 2001 From: swappy Date: Wed, 2 Apr 2025 17:21:39 +0530 Subject: [PATCH 8/8] feat: MegaLinter static analysis and code linting (#1) * feat: megalinter for linting/static analysis * fix: render markdown & artifact link Signed-off-by: swappy <59965507+rycerzes@users.noreply.github.com> --- .github/workflows/release.yml | 1 + action.yml | 42 ++++++++++++++--- main.go | 22 ++++++++- utils/megalinter_report.go | 89 +++++++++++++++++++++++++++++++++++ utils/pr_analysis.go | 2 +- 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 utils/megalinter_report.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b681f3a..2c5ef7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,7 @@ on: branches: - main - func + - lint name: Create Release diff --git a/action.yml b/action.yml index b878269..6bc9c7c 100644 --- a/action.yml +++ b/action.yml @@ -19,12 +19,12 @@ inputs: delay: description: Time to start application required: true - default: 10s + default: "10" container-name: description: Name of the container in case of "docker compose" command build-delay: description: Time to wait for docker container build - default: 50s + default: "50" runner-version: description: Version of the runner to use default: "latest" @@ -41,7 +41,36 @@ runs: echo "Working directory: ${{ inputs.working-directory }}" echo "Keploy path: ${{ inputs.keploy-path }}" shell: bash - + + - name: Run MegaLinter + shell: bash + run: | + docker run -v $(pwd):/tmp/lint \ + -e GITHUB_TOKEN=${{ inputs.github-token }} \ + -e REPORT_OUTPUT_FOLDER=/tmp/lint/megalinter-reports \ + -e DISABLE_ERRORS=true \ + -e PARALLEL=true \ + -e JSON_REPORTER=true \ + -e MARKDOWN_SUMMARY_REPORTER=true \ + -e EXCLUDED_DIRECTORIES="keploy,megalinter-reports" \ + -e SHOW_SKIPPED_LINTERS=false \ + -e SHOW_ELAPSED_TIME=true \ + oxsecurity/megalinter-cupcake:v8.5.0 + working-directory: ${{ inputs.working-directory }} + + - name: Upload MegaLinter Report + uses: actions/upload-artifact@v4 + with: + name: megalinter-report + path: ${{ inputs.working-directory }}/megalinter-reports/ + retention-days: 1 + id: upload-megalinter + + - name: Set MegaLinter artifact link + shell: bash + run: | + echo "MEGALINTER_ARTIFACT_URL=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" >> $GITHUB_ENV + - name: Download pre-built runner run: | echo "Downloading from: https://github.com/rycerzes/testGPT/releases/download/latest/keploy-runner" @@ -62,7 +91,8 @@ runs: CONTAINER_NAME: ${{ inputs.container-name }} BUILD_DELAY: ${{ inputs.build-delay }} GITHUB_TOKEN: ${{ inputs.github-token }} - + MEGALINTER_ARTIFACT_URL: ${{ env.MEGALINTER_ARTIFACT_URL }} + - id: keploy-test-report name: Process Test Results run: | @@ -75,7 +105,7 @@ runs: shell: bash env: WORKDIR: ${{ inputs.working-directory }} - + - name: Check if report is generated run: | if [ -f "${GITHUB_WORKSPACE}/${WORKDIR}/final.out" ]; then @@ -87,7 +117,7 @@ runs: shell: bash env: WORKDIR: ${{ inputs.working-directory }} - + - name: Comment on PR if: success() && github.event_name == 'pull_request' uses: actions/github-script@v6 diff --git a/main.go b/main.go index cc02460..4016419 100644 --- a/main.go +++ b/main.go @@ -336,6 +336,18 @@ func processTestReports(githubWorkspace, workDir string) { fmt.Println("Not running in a PR context, running in manual trigger mode") } + // Get MegaLinter report if available + var megaLinterMarkdown string + megaLinterSummary, err := utils.GetMegaLinterReport(githubWorkspace, workDir) + if err != nil { + fmt.Printf("Warning: Failed to read MegaLinter report: %v\n", err) + } else { + // Get the artifact link from environment variable + artifactLink := os.Getenv("MEGALINTER_ARTIFACT_URL") + megaLinterMarkdown = utils.FormatMegaLinterReport(megaLinterSummary, artifactLink) + fmt.Println("Successfully processed MegaLinter report") + } + outputDir := filepath.Join(githubWorkspace, workDir) if _, err := os.Stat(outputDir); os.IsNotExist(err) { os.MkdirAll(outputDir, 0755) @@ -399,11 +411,19 @@ func processTestReports(githubWorkspace, workDir string) { // Add PR details only if available in PR context if isPRContext && prDetailsMarkdown != "" { githubOutputBuilder.WriteString("
\n") - githubOutputBuilder.WriteString("**🔍 PR Analysis**\n\n") + githubOutputBuilder.WriteString("

🔍 PR Analysis

\n\n") githubOutputBuilder.WriteString(prDetailsMarkdown) githubOutputBuilder.WriteString("
\n\n") } + // Add MegaLinter report if available + if megaLinterMarkdown != "" { + githubOutputBuilder.WriteString("
\n") + githubOutputBuilder.WriteString("

🔍 MegaLinter Analysis

\n\n") + githubOutputBuilder.WriteString(megaLinterMarkdown) + githubOutputBuilder.WriteString("
\n\n") + } + githubOutputBuilder.WriteString("EOF\n") os.WriteFile(filepath.Join(outputDir, "github_output.txt"), []byte(githubOutputBuilder.String()), 0644) diff --git a/utils/megalinter_report.go b/utils/megalinter_report.go new file mode 100644 index 0000000..6a8307d --- /dev/null +++ b/utils/megalinter_report.go @@ -0,0 +1,89 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +type MegaLinterSummary struct { + Status string + Table string + ReportID string +} + +func GetMegaLinterReport(githubWorkspace, workDir string) (*MegaLinterSummary, error) { + reportPath := filepath.Join(githubWorkspace, workDir, "megalinter-reports", "megalinter-report.md") + + data, err := os.ReadFile(reportPath) + if err != nil { + return nil, fmt.Errorf("failed to read MegaLinter report: %v", err) + } + + content := string(data) + + status := "UNKNOWN" + if strings.Contains(content, "⚠️ WARNING") { + status = "WARNING" + } else if strings.Contains(content, "❌ ERROR") { + status = "ERROR" + } else if strings.Contains(content, "✅ SUCCESS") { + status = "SUCCESS" + } + + lines := strings.Split(content, "\n") + var tableLines []string + inTable := false + + for _, line := range lines { + if strings.HasPrefix(line, "|") { + inTable = true + tableLines = append(tableLines, line) + } else if inTable && len(line) == 0 { + break + } + } + + table := strings.Join(tableLines, "\n") + + reportID := fmt.Sprintf("megalinter-report-%d", os.Getpid()) + + return &MegaLinterSummary{ + Status: status, + Table: table, + ReportID: reportID, + }, nil +} + +func FormatMegaLinterReport(summary *MegaLinterSummary, artifactLink string) string { + var sb strings.Builder + + statusEmoji := "⚠️" + if summary.Status == "SUCCESS" { + statusEmoji = "✅" + } else if summary.Status == "ERROR" { + statusEmoji = "❌" + } + + sb.WriteString(fmt.Sprintf("### %s **MegaLinter Results**\n\n", statusEmoji)) + + sb.WriteString(summary.Table) + + actionsLink := fmt.Sprintf("https://github.com/%s/actions/runs/%s", + os.Getenv("GITHUB_REPOSITORY"), + os.Getenv("GITHUB_RUN_ID")) + + sb.WriteString(fmt.Sprintf("\n- [MegaLinter Full Report](%s)\n", actionsLink)) + + // Use the provided artifact link instead of constructing one + if artifactLink != "" { + sb.WriteString(fmt.Sprintf("- [Download MegaLinter Report](%s)\n", artifactLink)) + } + + return sb.String() +} + +func GetGitHubRunID() string { + return os.Getenv("GITHUB_RUN_ID") +} diff --git a/utils/pr_analysis.go b/utils/pr_analysis.go index 862d792..e77e5f5 100644 --- a/utils/pr_analysis.go +++ b/utils/pr_analysis.go @@ -112,7 +112,7 @@ func FormatPRDetailsForComment(pr *PRDetails) string { sb.WriteString(fmt.Sprintf("**Updated At**: %s\n\n", pr.UpdatedAt)) sb.WriteString("
\n") - sb.WriteString("**🔍 PR Analysis Details**\n\n") + sb.WriteString("

🔍 PR Analysis Details

\n\n") sb.WriteString("#### **Changed Files**\n\n") for _, file := range pr.ChangedFiles {