diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..bf678374 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,181 @@ +name: Unit and Integration Tests for Apache Cassandra NodeJS Driver + +on: + push: + pull_request: + workflow_dispatch: # allow manual trigger from Actions page +permissions: + contents: read + actions: read + checks: write +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + NODE_VERSION: ["20", "22", "24"] + SERVER_VERSION: ["3.11", "4.0", "4.1", "5.0"] # Cassandra minor versions + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # ---- Python for ccm ---- + - name: Set up Python 3.9.16 + uses: actions/setup-python@v5 + with: + python-version: "3.9.16" + + - name: Install ccm + run: | + python -m pip install --upgrade pip + git clone --depth 1 --single-branch -b cassandra-test https://github.com/apache/cassandra-ccm.git + cd cassandra-ccm + pip install -r requirements.txt + ./setup.py install + + # ---- Install required Zulu JDKs (8/11/17) and capture their homes ---- + - name: Install Zulu 11 + id: z11 + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: "11" + + - name: Install Zulu 17 + id: z17 + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: "17" + + - name: Install Zulu 8 + id: z8 + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: "8" + + - name: Export JAVA*_HOME variables + run: | + echo "JAVA8_HOME=${{ steps.z8.outputs.path }}" >> "$GITHUB_ENV" + echo "JAVA11_HOME=${{ steps.z11.outputs.path }}" >> "$GITHUB_ENV" + echo "JAVA17_HOME=${{ steps.z17.outputs.path }}" >> "$GITHUB_ENV" + echo "JAVA_HOME=${{ steps.z8.outputs.path }}" >> "$GITHUB_ENV" + + - name: Generate SSL certificates + run: | + mkdir -p /home/runner/workspace/tools/ccm/ssl/ + cd /home/runner/workspace/tools/ccm/ssl/ + keytool -genkey \ + -keyalg RSA \ + -alias cassandra \ + -keystore keystore.jks \ + -storepass cassandra \ + -keypass cassandra \ + -validity 364635 \ + -dname "CN=Philip Thompson, OU=TE, O=DataStax, L=Santa Clara, ST=CA, C=TE" + keytool -export \ + -alias cassandra \ + -file cassandra.crt \ + -keystore keystore.jks \ + -storepass cassandra + openssl x509 \ + -inform der \ + -in cassandra.crt \ + -out cassandra.pem + keytool -genkeypair \ + -keyalg RSA \ + -alias client \ + -keystore truststore.jks \ + -storepass cassandra \ + -keypass cassandra \ + -validity 364635 \ + -dname "CN=Philip Thompson, OU=TE, O=DataStax, L=Santa Clara, ST=CA, C=TE" + keytool -importkeystore \ + -srckeystore truststore.jks \ + -destkeystore client.p12 \ + -srcstorepass cassandra \ + -deststorepass cassandra \ + -deststoretype PKCS12 + openssl pkcs12 \ + -in client.p12 \ + -passin pass:cassandra \ + -nokeys \ + -out client_cert.pem -legacy + openssl pkcs12 \ + -in client.p12 \ + -passin pass:cassandra \ + -nodes \ + -nocerts \ + -out client_key.pem -legacy + + - name: Install Simulacron + run: | + wget https://github.com/datastax/simulacron/releases/download/0.12.0/simulacron-standalone-0.12.0.jar -O /home/runner/simulacron.jar + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.NODE_VERSION }} + cache: "npm" + + - name: Resolve Cassandra latest patch version + env: + SERVER_VERSION: ${{ matrix.SERVER_VERSION }} + run: | + PATCH_SERVER_VERSION=$( + curl -s https://downloads.apache.org/cassandra/ \ + | grep -oP '(?<=href=")[0-9]+\.[0-9]+\.[0-9]+(?=)' \ + | sort -rV \ + | uniq -w 3 \ + | grep "^${SERVER_VERSION}\." + ) + echo "Resolved Cassandra ${SERVER_VERSION}.x -> ${PATCH_SERVER_VERSION}" + echo "CCM_VERSION=$PATCH_SERVER_VERSION" >> "$GITHUB_ENV" + + - name: Print environment + run: printenv | sort + + - name: Run tests + run: | + npm install + npm install --no-save mocha-multi-reporters mocha-junit-reporter + echo '{ + "reporterEnabled": "spec, mocha-junit-reporter", + "mochaJunitReporterReporterOptions": { + "mochaFile": "test-results/results.xml" + } + }' > config.json + + npx mocha test/unit test/integration/short --recursive --exit --reporter mocha-multi-reporters \ + --reporter-options configFile=config.json + + - name: Upload Test Results + if: (!cancelled()) + uses: actions/upload-artifact@v4 + with: + name: Test Results (Node.js ${{ matrix.NODE_VERSION }}, Cassandra ${{ matrix.SERVER_VERSION }}) + path: test-results/**/* + + publish-test-results: + name: "Publish Tests Results" + needs: test + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + + if: (!cancelled()) + + steps: + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: artifacts/**/*.xml diff --git a/test/integration/short/metadata-tests.js b/test/integration/short/metadata-tests.js index eb86a757..4ef8323a 100644 --- a/test/integration/short/metadata-tests.js +++ b/test/integration/short/metadata-tests.js @@ -184,14 +184,20 @@ describe('metadata @SERVER_API', function () { }); }); describe('#getTokenRanges()', function () { - it('should return 512 ranges', function (done) { - // as vnodes are enabled and there are 2 nodes, expect 512 (2* 256) ranges. + it('should return the correct number of ranges', function (done) { const client = newInstance(); utils.series([ client.connect.bind(client), - function getRanges(next) { + async function getRanges(next) { + // Query system tables to get actual tokens + const localRs = await client.execute('SELECT tokens FROM system.local'); + let tokenCount = localRs.rows[0].tokens.length; + const peersRs = await client.execute('SELECT tokens FROM system.peers'); + peersRs.rows.forEach(row => { + if (row.tokens) tokenCount += row.tokens.length; + }); const ranges = client.metadata.getTokenRanges(); - assert.strictEqual(ranges.size, 512); + assert.strictEqual(ranges.size, tokenCount); next(); }, client.shutdown.bind(client) @@ -206,18 +212,24 @@ describe('metadata @SERVER_API', function () { helper.toTask(client.execute, client, "CREATE KEYSPACE ksrf1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1}"), helper.toTask(client.execute, client, "CREATE KEYSPACE ksrf2 WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 2}"), helper.toTask(client.execute, client, "CREATE KEYSPACE ksntsrf2 WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1' : 2}"), - function getRanges(next) { + async function getRanges(next) { + // Query system tables to get actual tokens + const localRs = await client.execute('SELECT tokens FROM system.local'); + let tokenCount = localRs.rows[0].tokens.length; + const peersRs = await client.execute('SELECT tokens FROM system.peers'); + peersRs.rows.forEach(row => { + if (row.tokens) tokenCount += row.tokens.length; + }); const host1 = helper.findHost(client, 1); const host2 = helper.findHost(client, 2); // the sum of ranges between host1 and host2 should be the total number of tokens. - // we can't make an exact assertion here because token assignment is not exact. const rf1Ranges = client.metadata.getTokenRangesForHost('ksrf1', host1).size + client.metadata.getTokenRangesForHost('ksrf1', host2).size; - assert.strictEqual(rf1Ranges, 512); - // expect 512 ranges for each host (2 replica = 512 tokens) - assert.strictEqual(client.metadata.getTokenRangesForHost('ksrf2', host1).size, 512); - assert.strictEqual(client.metadata.getTokenRangesForHost('ksrf2', host2).size, 512); - assert.strictEqual(client.metadata.getTokenRangesForHost('ksntsrf2', host1).size, 512); - assert.strictEqual(client.metadata.getTokenRangesForHost('ksntsrf2', host2).size, 512); + assert.strictEqual(rf1Ranges, tokenCount); + // expect tokenCount ranges for each host (2 replica = tokenCount tokens) + assert.strictEqual(client.metadata.getTokenRangesForHost('ksrf2', host1).size, tokenCount); + assert.strictEqual(client.metadata.getTokenRangesForHost('ksrf2', host2).size, tokenCount); + assert.strictEqual(client.metadata.getTokenRangesForHost('ksntsrf2', host1).size, tokenCount); + assert.strictEqual(client.metadata.getTokenRangesForHost('ksntsrf2', host2).size, tokenCount); next(); }, client.shutdown.bind(client) diff --git a/test/unit/license-tests.js b/test/unit/license-tests.js index 79445b3e..968ebafc 100644 --- a/test/unit/license-tests.js +++ b/test/unit/license-tests.js @@ -20,25 +20,23 @@ const assert = require("assert"); const path = require("path"); const fs = require("fs"); -const licenseHeaderRegex = new RegExp( - `/\\* - \\* Licensed to the Apache Software Foundation (ASF) under one +const licenseHeaderRegex = new RegExp(`\\\/\\* + \\* 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 + \\* 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 + \\* 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\\. - \\*/` -); + \\*\\\/`); describe('All source files', function() { this.timeout(5000);