diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index 5c5dc5dc84c7..2adc5d67a2ca 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -49,24 +49,26 @@ runs: shell: bash run: sudo apt-get install libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev - - name: Setup protoc on linux - if: inputs.need-protoc == 'true' && runner.os == 'Linux' - shell: bash - run: sudo apt install -y protobuf-compiler + - name: Setup Protoc + if: inputs.need-protoc == 'true' + uses: arduino/setup-protoc@v2 + with: + version: "23.4" - name: Cache nextest on linux id: cache-nextest uses: actions/cache@v3 - if: inputs.need-nextest == 'true' && runner.os == 'Linux' + if: inputs.need-nextest == 'true' with: - path: ~/.cargo/bin/cargo-nextest - # TODO: remove the runner.os while bumping version - key: r0-${{runner.os}}-nextest-0.9.53 + path: | + ~/.cargo/bin/cargo-nextest + ~/.cargo/bin/cargo-nextest.exe + key: r0-${{runner.os}}-nextest-0.9.59 - name: Build nextest if not cached if: steps.cache-nextest.outputs.cache-hit != 'true' && inputs.need-nextest == 'true' shell: bash - run: cargo install cargo-nextest@0.9.53 --locked + run: cargo install cargo-nextest@0.9.59 --locked - name: Setup rust on linux if: runner.os == 'Linux' && inputs.need-rocksdb == 'true' diff --git a/.github/actions/test-core/action.yaml b/.github/actions/test-core/action.yaml new file mode 100644 index 000000000000..da5759fe14ed --- /dev/null +++ b/.github/actions/test-core/action.yaml @@ -0,0 +1,51 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Test Core +description: 'Test Core with given setup and service' +inputs: + setup: + description: "The setup action for test" + service: + description: "The service to test" + secrets: + description: "The secrets for test" + default: "{}" + +runs: + using: "composite" + steps: + - name: Setup + shell: bash + run: | + mkdir -p ./dynamic_test_core && + cat <./dynamic_test_core/action.yml + runs: + using: composite + steps: + - name: Setup Test Core + uses: ./.github/services/${{ inputs.service }}/${{ inputs.setup }} + with: + secrets: ${{ toJson(inputs.secrets) }} + - name: Run Test Core + shell: bash + working-directory: core + run: cargo nextest run services_${{ inputs.service }} --archive-file=./core-test.tar.zst + EOF + - name: Run + id: run + uses: ./dynamic_test_core diff --git a/.github/services/fs/local-fs/action.yml b/.github/services/fs/local-fs/action.yml new file mode 100644 index 000000000000..4175de1eb822 --- /dev/null +++ b/.github/services/fs/local-fs/action.yml @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: local_fs +description: "Setup local fs" + +runs: + using: "composite" + steps: + - name: Setup + shell: bash + run: | + echo "OPENDAL_FS_TEST=on" >> $GITHUB_ENV + echo "OPENDAL_FS_ROOT=${{ runner.temp }}/" >> $GITHUB_ENV diff --git a/.github/services/s3/aws-s3/action.yml b/.github/services/s3/aws-s3/action.yml new file mode 100644 index 000000000000..7591937d5a81 --- /dev/null +++ b/.github/services/s3/aws-s3/action.yml @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: aws_s3 +description: "Setup AWS S3" + +inputs: + secrets: + description: "The secrets for test" + default: "{}" + +# TODO: we should migrate to 1password instead. +runs: + using: "composite" + steps: + - name: Setup + shell: bash + run: | + echo "OPENDAL_S3_TEST=${{ fromJson(inputs.secrets).OPENDAL_S3_TEST }}" >> $GITHUB_ENV + echo "OPENDAL_S3_ROOT=${{ fromJson(inputs.secrets).OPENDAL_S3_ROOT }}" >> $GITHUB_ENV + echo "OPENDAL_S3_BUCKET=${{ fromJson(inputs.secrets).OPENDAL_S3_BUCKET }}" >> $GITHUB_ENV + echo "OPENDAL_S3_ENDPOINT=${{ fromJson(inputs.secrets).OPENDAL_S3_ENDPOINT }}" >> $GITHUB_ENV + echo "OPENDAL_S3_ACCESS_KEY_ID=${{ fromJson(inputs.secrets).OPENDAL_S3_ACCESS_KEY_ID }}" >> $GITHUB_ENV + echo "OPENDAL_S3_SECRET_ACCESS_KEY=${{ fromJson(inputs.secrets).OPENDAL_S3_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV + echo "OPENDAL_S3_REGION=ap-northeast-1" >> $GITHUB_ENV diff --git a/.github/workflows/core_test.yml b/.github/workflows/core_test.yml new file mode 100644 index 000000000000..a92cadd9a3f6 --- /dev/null +++ b/.github/workflows/core_test.yml @@ -0,0 +1,93 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Core + +on: + workflow_call: + inputs: + os: + required: true + type: string + cases: + required: true + type: string + features: + required: true + type: string + +jobs: + build: + name: build + runs-on: ${{ inputs.os }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust toolchain + uses: ./.github/actions/setup + with: + need-nextest: true + need-protoc: true + need-rocksdb: true + + # Required by hdfs + - name: Setup java env + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: "11" + + - name: Build + shell: bash + working-directory: core + run: | + cargo nextest archive --features ${{ inputs.features }} --archive-file ./core-test.tar.zst + + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.os }}-core-test + path: ./core/core-test.tar.zst + + test: + name: ${{ matrix.cases.service }} / ${{ matrix.cases.setup }} + needs: [build] + runs-on: ${{ inputs.os }} + strategy: + matrix: + cases: ${{ fromJson(inputs.cases) }} + steps: + - uses: actions/checkout@v4 + - name: Setup Rust toolchain + uses: ./.github/actions/setup + with: + need-nextest: true + need-protoc: true + need-rocksdb: true + + - name: Download + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.os }}-core-test + path: ./core + + - name: Test Core + uses: ./.github/actions/test-core + with: + setup: ${{ matrix.cases.setup }} + service: ${{ matrix.cases.service }} + secrets: ${{ toJson(secrets) }} diff --git a/.github/workflows/service_test_fs.yml b/.github/workflows/service_test_fs.yml deleted file mode 100644 index c06b9e81d02b..000000000000 --- a/.github/workflows/service_test_fs.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -name: Service Test Fs - -on: - push: - branches: - - main - pull_request: - branches: - - main - paths: - - "core/src/**" - - "core/tests/**" - - "!core/src/docs/**" - - "!core/src/services/**" - - "core/src/services/fs/**" - - ".github/workflows/service_test_fs.yml" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: true - -jobs: - local_fs: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - windows-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Rust toolchain - uses: ./.github/actions/setup - - name: Test - shell: bash - working-directory: core - run: cargo test services_fs - env: - OPENDAL_FS_TEST: on - OPENDAL_FS_ROOT: ${{ runner.temp }}/ diff --git a/.github/workflows/service_test_s3.yml b/.github/workflows/service_test_s3.yml index 9efd04f60aaf..95f703ab7095 100644 --- a/.github/workflows/service_test_s3.yml +++ b/.github/workflows/service_test_s3.yml @@ -39,28 +39,6 @@ concurrency: cancel-in-progress: true jobs: - aws_s3: - runs-on: ubuntu-latest - if: github.event_name == 'push' || !github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@v4 - - name: Setup Rust toolchain - uses: ./.github/actions/setup - with: - need-nextest: true - - name: Test - shell: bash - working-directory: core - run: cargo nextest run s3 - env: - OPENDAL_S3_TEST: ${{ secrets.OPENDAL_S3_TEST }} - OPENDAL_S3_ROOT: ${{ secrets.OPENDAL_S3_ROOT }} - OPENDAL_S3_BUCKET: ${{ secrets.OPENDAL_S3_BUCKET }} - OPENDAL_S3_ENDPOINT: ${{ secrets.OPENDAL_S3_ENDPOINT }} - OPENDAL_S3_ACCESS_KEY_ID: ${{ secrets.OPENDAL_S3_ACCESS_KEY_ID }} - OPENDAL_S3_SECRET_ACCESS_KEY: ${{ secrets.OPENDAL_S3_SECRET_ACCESS_KEY }} - OPENDAL_S3_REGION: ap-northeast-1 - aws_s3_with_virtual_host: runs-on: ubuntu-latest if: github.event_name == 'push' || !github.event.pull_request.head.repo.fork diff --git a/.github/workflows/test_planner.yml b/.github/workflows/test_planner.yml new file mode 100644 index 000000000000..535170d863ca --- /dev/null +++ b/.github/workflows/test_planner.yml @@ -0,0 +1,94 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + plan: + runs-on: ubuntu-latest + outputs: + plan: ${{ steps.plan.outputs.plan }} + + steps: + - uses: actions/checkout@v4 + with: + # fetch depth set to 0 to make sure we have correct diff result. + fetch-depth: 0 + + - name: Plan + id: plan + run: | + git fetch origin main:main + + event_name="${{ github.event_name }}" + repository="${{ github.repository }}" + files_changed="" + has_secrets="False" + is_push="False" + + # Handle event-specific logic + if [ "$event_name" == "push" ]; then + if [ "$repository" == "apache/incubator-opendal" ]; then + is_push="True" + has_secrets="True" + fi + elif [ "$event_name" == "pull_request" ]; then + pr_head_repo_fork="${{ github.event.pull_request.head.repo.fork }}" + if [ "$pr_head_repo_fork" != "true" ]; then + has_secrets="True" + fi + + files_changed=$(git diff --name-only main ${{ github.event.pull_request.head.sha }}) + echo "Files changed:" + echo "$FILES_CHANGED" + fi + + # Export variables + export GITHUB_HAS_SECRETS=$has_secrets + export GITHUB_IS_PUSH=$is_push + + # Run the workflow planner script + PLAN=$(./scripts/workflow_planner.py $files_changed) + echo "Plan:" + echo "$PLAN" | jq . + echo "plan=$PLAN" >> $GITHUB_OUTPUT + + core_test: + name: core / ${{ matrix.os }} + needs: [plan] + if: fromJson(needs.plan.outputs.plan).components.core + secrets: inherit + strategy: + matrix: + include: ${{ fromJson(needs.plan.outputs.plan).core }} + uses: ./.github/workflows/core_test.yml + with: + os: ${{ matrix.os }} + cases: ${{ toJson(matrix.cases) }} + features: ${{ matrix.features }} diff --git a/scripts/workflow_planner.py b/scripts/workflow_planner.py new file mode 100755 index 000000000000..7c8d8f9d71e5 --- /dev/null +++ b/scripts/workflow_planner.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import sys +import json +import os +from pathlib import Path + + +def get_provided_cases(): + root_dir = ".github/services" + + cases = [ + { + "service": service, + "setup": setup, + "content": Path( + os.path.join(root_dir, service, setup, "action.yml") + ).read_text(), + } + for service in os.listdir(root_dir) + for setup in os.listdir(os.path.join(root_dir, service)) + ] + + # Check if this workflow needs to read secrets. + # + # We will check if pattern `secrets.XXX` exist in content. + if not bool(os.getenv("GITHUB_HAS_SECRETS")): + cases[:] = [v for v in cases if "secrets." not in v["content"]] + + return cases + + +def calculate_core_cases(cases, changed_files): + # If any of the core workflow changed, we will run all cases. + for p in [ + ".github/workflows/core_test.yml", + ".github/workflows/test_planner.yml", + ".github/actions/test-core", + ]: + if p in changed_files: + return cases + + # Always run all tests if it is a push event. + if bool(os.getenv("GITHUB_IS_PUSH")): + return cases + + # If any of the core files changed, we will run all cases. + if any( + p.startswith("core/src/") and not p.startswith("core/src/services") + for p in changed_files + ): + return cases + if any(p.startswith("core/tests/") for p in changed_files): + return cases + + # Filter all cases that not shown un in changed files + cases = [v for v in cases if any(v["service"] in p for p in changed_files)] + return cases + + +# Context is the github context: https://docs.github.com/en/actions/learn-github-actions/contexts#github-context +def plan(changed_files): + # TODO: add bindings/java, bindings/python in the future. + components = ["core"] + cases = get_provided_cases() + + core_cases = calculate_core_cases(cases, changed_files) + + jobs = {} + + if len(core_cases) > 0: + jobs["components"] = {"core": True} + jobs["core"] = [ + { + "os": "ubuntu-latest", + "features": ",".join( + set([f"services-{v['service']}" for v in core_cases]) + ), + "cases": [ + {"setup": v["setup"], "service": v["service"]} for v in core_cases + ], + }, + ] + + # fs is the only services need to run upon windows, let's hard code it here. + if "fs" in [v["service"] for v in core_cases]: + jobs["core"].append( + { + "os": "windows-latest", + "features": "services-fs", + "cases": [{"setup": "local-fs", "service": "fs"}], + } + ) + + return json.dumps(jobs) + + +# For quick test: +# +# ./scripts/workflow_planner.py PATH +if __name__ == "__main__": + changed_files = sys.argv[1:] + result = plan(changed_files) + print(result)