From fa31c27e46c3d7b90e9581de116aae9497138687 Mon Sep 17 00:00:00 2001 From: Aleksander Zaruczewski Date: Tue, 27 Feb 2024 21:04:10 +0200 Subject: [PATCH] test(acctest): incremental run support --- .../incremental-acceptance-tests.yml | 297 ++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 .github/workflows/incremental-acceptance-tests.yml diff --git a/.github/workflows/incremental-acceptance-tests.yml b/.github/workflows/incremental-acceptance-tests.yml new file mode 100644 index 000000000..ad27e1362 --- /dev/null +++ b/.github/workflows/incremental-acceptance-tests.yml @@ -0,0 +1,297 @@ +name: Incremental Acceptance Tests + +on: + pull_request: + branches: + - main + - v* + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + push: + branches: + - main + - v* + workflow_dispatch: {} + +permissions: read-all + +concurrency: + group: ci-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + selproj: + name: Run selproj + runs-on: ubuntu-latest + if: > + (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip workflows')) || + github.event_name == 'push' + + outputs: + suffix: ${{ steps.selproj.outputs.suffix }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - id: selproj + run: echo "suffix=$(make -s ci-selproj | tr -d '\n')" >> $GITHUB_OUTPUT + env: + AIVEN_TOKEN: ${{ secrets.AIVEN_TOKEN }} + AIVEN_PROJECT_NAME_PREFIX: ${{ secrets.AIVEN_PROJECT_NAME_PREFIX }} + + parse_sdkv2_files: + name: Parse SDKv2 resource and datasource files + runs-on: ubuntu-latest + if: > + (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip workflows')) || + github.event_name == 'push' + + env: + SDKV2_IMPL_PATH: internal/sdkprovider + + outputs: + files: ${{ steps.parse_files.outputs.files }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Find provider.go file + id: find_provider_go_file + run: | + echo "path=$(find "$SDKV2_IMPL_PATH" -type f -name 'provider.go')" >> $GITHUB_OUTPUT + + - name: Extract imports + id: extract_imports + run: | + provider_go_file=${{ steps.find_provider_go_file.outputs.path }} + imports=$(awk '/^import \(/{flag=1;next}/^\)/{flag=0}flag' "$provider_go_file") + imports=$(echo "$imports" | jq -R -s -c 'split("\n") | map(select(length > 0) | gsub("[[:space:]\"]";""))') + echo "imports=$imports" >> $GITHUB_OUTPUT + + - name: Build import map + id: build_import_map + run: | + import_lines=$(echo '${{ steps.extract_imports.outputs.imports }}' | jq -r '.[]') + import_map="{}" + + while IFS= read -r line; do + if [[ $line =~ ^(.*)"$SDKV2_IMPL_PATH"/(.*)$ ]]; then + package_path="${BASH_REMATCH[2]}" + package_name=$(basename "$package_path") + + echo "Found package $package_name in $SDKV2_IMPL_PATH/$package_path" + import_map=$( + echo "$import_map" | jq -c --arg key "$package_name" --arg value "$package_path" \ + '. + {($key): $value}' + ) + fi + done <<< "$import_lines" + + echo "import_map=$import_map" >> $GITHUB_OUTPUT + + - name: Parse files + id: parse_files + run: | + provider_go_file=${{ steps.find_provider_go_file.outputs.path }} + import_map='${{ steps.build_import_map.outputs.import_map }}' + files="[]" + + while read -r name type func_with_parentheses; do + if [[ -z "$name" || -z "$func_with_parentheses" ]]; then + continue + fi + + func="${func_with_parentheses%)*}" + func="${func##*.}" + package_name="${func_with_parentheses%%.*}" + path=$(echo $import_map | jq -r --arg package_name "$package_name" '.[$package_name] // empty') + + if [[ -n "$path" ]]; then + path="$SDKV2_IMPL_PATH/$path" + file=$(find "$path" -type f -name '*.go' -exec grep -l "$func" {} +) + + if [[ -n "$file" ]]; then + echo "Found $type $name in $file" + files=$( + echo "$files" | jq -c --arg name "$name" --arg type "$type" --arg file "$file" \ + '. + [{($name): {("type"): $type, ("file"): $file}}]' + ) + fi + fi + done < <(awk -v file="$provider_go_file" ' + /ResourcesMap: map\[string\]\*schema\.Resource{/ { type="resource"; capture = 1; next } + /DataSourcesMap: map\[string\]\*schema\.Resource{/ { type="datasource"; capture = 1; next } + capture && /\}/ { capture = 0 } + capture && NF { + gsub(/[\t "]+/, "") + split($0, a, ":") + print a[1], type, a[2] + } + ' "$provider_go_file") + + echo "files=$files" >> $GITHUB_OUTPUT + + parse_plugin_framework_files: + name: Parse Plugin Framework resource and datasource files + runs-on: ubuntu-latest + + needs: parse_sdkv2_files + + env: + PLUGIN_FRAMEWORK_IMPL_PATH: internal/plugin + + outputs: + files: ${{ steps.parse_files.outputs.files }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Parse files + id: parse_files + run: | + files='${{ needs.parse_sdkv2_files.outputs.files }}' + + while IFS= read -r file; do + while IFS=, read -r name type file; do + echo "Found $type $name in $file" + files=$( + echo "$files" | jq -c --arg name "$name" --arg type "$type" --arg file "$file" \ + '. + [{($name): {("type"): $type, ("file"): $file}}]' + ) + done < <(awk -v file="$file" ' + /resp\.TypeName\s*=\s*req\.ProviderTypeName\s*\+\s*"/ { + match($0, /"(_[^"]+)"/, arr) + if (length(arr[1]) > 0) { + type = "datasource" + while((getline line < file) > 0) { + if (line ~ /_ resource\.Resource/) { + type = "resource" + break + } + } + close(file) + print "aiven" arr[1] "," type "," file + } + } + ' "$file") + done < <(find "$PLUGIN_FRAMEWORK_IMPL_PATH" -type f -name '*.go') + + echo "files=$files" >> $GITHUB_OUTPUT + + find_tests: + name: Find tests + runs-on: ubuntu-latest + + needs: + - parse_plugin_framework_files + + outputs: + tests: ${{ steps.find_tests.outputs.tests }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Find tests + id: find_tests + run: | + tests=() + mapfile -t parsed_files < <(echo '${{ needs.parse_plugin_framework_files.outputs.files }}' | jq -c '.[]') + + for parsed_file in "${parsed_files[@]}"; do + name=$(echo "$parsed_file" | jq -r 'keys[0]') + type=$(echo "$parsed_file" | jq -r --arg name "$name" '.[$name].type') + file=$(echo "$parsed_file" | jq -r --arg name "$name" '.[$name].file') + dir=$(dirname "$file") + + file_changed=false + dep_changed=false + + if git diff "origin/${{ github.base_ref || 'main' }}" --name-only | grep -q "^$file$"; then + echo "Definition of $name $type in $file changed" + file_changed=true + else + deps=$(go list -json "./$dir" | jq -r '.Deps[]' | grep -oP '^github.com/aiven/terraform-provider-aiven/\K.*') + for dep in $deps; do + if git diff "origin/${{ github.base_ref || 'main' }}" --name-only | grep -q "$dep"; then + echo "Dependency $dep of $name $type changed" + dep_changed=true + break + fi + done + fi + + if [ "$file_changed" = true ] || [ "$dep_changed" = true ]; then + pattern="" + [[ "$type" == "resource" ]] && pattern="resource \"$name\"" + [[ "$type" == "datasource" ]] && pattern="data \"$name\"" + + while IFS= read -r test; do + if grep -q "$pattern" "$test"; then + echo "Found test for $name $type in $test" + tests+=("$test") + fi + done < <(find internal -type f -name '*_test.go' -exec grep -l "$pattern" {} +) + fi + done + + readarray -t unique_tests < <(printf '%s\n' "${tests[@]}" | awk '!seen[$0]++') + tests_json=$(printf '%s\n' "${unique_tests[@]}" | jq -R . | jq -s -c .) + echo "tests=$tests_json" >> $GITHUB_OUTPUT + + run_tests: + name: Run tests + runs-on: ubuntu-latest + + needs: + - selproj + - find_tests + + env: + COUNT: 1 + PARALLEL: 10 + TIMEOUT: 180m + + strategy: + max-parallel: 5 + matrix: + test: ${{ fromJson(needs.find_tests.outputs.tests) }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Run tests + run: go test ${{ matrix.test }} -v -count $COUNT -parallel $PARALLEL -timeout $TIMEOUT + env: + TF_ACC: 1 + CGO_ENABLED: 0 + AIVEN_TOKEN: ${{ secrets.AIVEN_TOKEN }} + AIVEN_PROJECT_NAME: ${{ secrets.AIVEN_PROJECT_NAME_PREFIX }}${{ needs.selproj.outputs.suffix }} + AIVEN_ORGANIZATION_NAME: ${{ secrets.AIVEN_ORGANIZATION_NAME }} + AIVEN_ACCOUNT_NAME: ${{ secrets.AIVEN_ORGANIZATION_NAME }}