diff --git a/.github/workflows/axon-start-test.yml b/.github/workflows/axon-start-test.yml index 15876aeb5..d58ae11ec 100644 --- a/.github/workflows/axon-start-test.yml +++ b/.github/workflows/axon-start-test.yml @@ -52,8 +52,6 @@ jobs: --config devtools/chain/config.toml \ | tee -a ${{ env.LOG_FILE }} & - sleep 10 - npx zx <<'EOF' import { waitXBlocksPassed } from './devtools/ci/scripts/helper.js' await retry(3, '6s', () => waitXBlocksPassed('http://127.0.0.1:8000', 2)) diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 96dbe0659..1ebfa80f4 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -38,16 +38,29 @@ jobs: restore-keys: | ${{ matrix.os }}-${{ runner.os }}-${{ runner.arch }}-cargo-build - - uses: lyricwulf/abc@v1 - with: - # https://www.gnu.org/software/m4/ - linux: m4 + - name: Start a single Axon node + env: + LOG_FILE: ${{ runner.temp }}/${{ matrix.os }}-single-axon-node.log + run: | + target/debug/axon --version | tee ${{ env.LOG_FILE }} + target/debug/axon init \ + --config devtools/chain/config.toml \ + --chain-spec devtools/chain/specs/single_node/chain-spec.toml \ + | tee -a ${{ env.LOG_FILE }} + target/debug/axon run \ + --config devtools/chain/config.toml \ + | tee -a ${{ env.LOG_FILE }} & + + npx zx <<'EOF' + import { waitXBlocksPassed } from './devtools/ci/scripts/helper.js' + await retry(3, '6s', () => waitXBlocksPassed('http://127.0.0.1:8000', 2)) + EOF + timeout-minutes: 1 - # TODO: use Node.js 18 - name: Use Node.js 16 uses: actions/setup-node@v4 with: - node-version: "16" + node-version: 16 - name: Get yarn cache directory id: yarn-cache-dir run: echo "dir=$(yarn cache dir)" >> ${GITHUB_OUTPUT} @@ -67,6 +80,17 @@ jobs: ${{ runner.os }}-node_modules- - name: E2E Tests Linting in tests/e2e - run: make e2e-test-lint + working-directory: tests/e2e + run: yarn && yarn lint + + - name: Serve files in tests/e2e/src by http-server + working-directory: tests/e2e + run: echo '\n' | yarn http-server + - name: E2E Tests in tests/e2e - run: make e2e-test-ci + working-directory: tests/e2e + run: | + npx zx <<'EOF' + await retry(3, expBackoff(), () => $`HEADLESS=new yarn test`) + EOF + timeout-minutes: 6 diff --git a/Makefile b/Makefile index 5707d7cd7..53872ab19 100644 --- a/Makefile +++ b/Makefile @@ -46,31 +46,6 @@ info: pwd env -e2e-test-lint: - cd tests/e2e && yarn && yarn lint - -e2e-test: - cargo build - rm -rf ./devtools/chain/data - ./target/debug/axon init \ - --config devtools/chain/config.toml \ - --chain-spec devtools/chain/specs/single_node/chain-spec.toml \ - > /tmp/log 2>&1 - ./target/debug/axon run \ - --config devtools/chain/config.toml \ - >> /tmp/log 2>&1 & - cd tests/e2e && yarn - cd tests/e2e/src && yarn exec http-server & - cd tests/e2e && yarn exec wait-on -t 5000 tcp:8000 && yarn exec wait-on -t 5000 tcp:8080 && HEADLESS="$(HEADLESS)" yarn test - pkill -2 axon - pkill -2 http-server - -e2e-test-ci: HEADLESS=true -e2e-test-ci: e2e-test - -e2e-test-via-docker: - docker-compose -f tests/e2e/docker-compose-e2e-test.yaml up --exit-code-from e2e-test --force-recreate - # For counting lines of code stats: @cargo count --version || cargo +nightly install --git https://github.com/kbknapp/cargo-count diff --git a/tests/e2e/config.js b/tests/e2e/config.js index 48da21fdb..96b8387af 100644 --- a/tests/e2e/config.js +++ b/tests/e2e/config.js @@ -1,80 +1,18 @@ import configSetting from "./config.json"; -function makeRequest(method, url) { - return new Promise((resolve, reject) => { - const XMLHttpRequest = require("xhr2");// eslint-disable-line global-require - const xhr = new XMLHttpRequest(); - xhr.open(method, url); - xhr.setRequestHeader("Content-type", "application/json"); - xhr.send(JSON.stringify( - { - id: 1, - jsonrpc: "2.0", - method: "eth_chainId", - params: [ - - ], - }, - )); - xhr.onload = function load_() { - if (this.status >= 200 && this.status < 300) { - resolve(xhr.response); - } else { - reject(new Error("makeRequest fail")); - } - }; - xhr.onerror = function error_() { - // reject({ - // status: this.status, - // statusText: xhr.statusText, - // }); - reject(new Error("makeRequest fail")); - }; - // xhr.send(); - }); -} - -async function doAjaxThings() { - const result = await makeRequest("POST", configSetting.axonRpc.url); - - return new Promise((resolve) => { - resolve(parseInt(JSON.parse(result).result, 16)); - }); -} - export default class Config { constructor() { - this.axonRpc = { url: "", netWorkName: "", chainId: "" }; - this.acount1 = ""; - this.acount2 = ""; - this.httpServer = ""; - this.hexPrivateKey = ""; - try { - // eslint-disable-next-line no-console - console.log(configSetting.axonRpc); - this.axonRpc = configSetting.axonRpc; - this.httpServer = configSetting.httpServer; - this.hexPrivateKey = configSetting.hexPrivateKey; - this.acount1 = configSetting.acount1; - this.acount2 = configSetting.acount2; - } catch (err) { - // eslint-disable-next-line no-console - console.log(err); - throw err; - } - } - - async initialize() { - this.axonRpc.chainId = await doAjaxThings(); + this.axonRpc = configSetting.axonRpc; + this.httpServer = configSetting.httpServer; + this.hexPrivateKey = configSetting.hexPrivateKey; + this.account1 = configSetting.account1; + this.account2 = configSetting.account2; } static getIns() { if (!Config.ins) { Config.ins = new Config(); } - (async function init_() { - await Config.ins.initialize(); - }()); return Config.ins; } } diff --git a/tests/e2e/config.json b/tests/e2e/config.json index 0b230d47a..1b23e15eb 100644 --- a/tests/e2e/config.json +++ b/tests/e2e/config.json @@ -3,9 +3,9 @@ "axonRpc": { "netWorkName": "axon", "url": "http://localhost:8000", - "chainId": 0 + "chainId": 1098411886 }, "hexPrivateKey": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - "acount1": "0x8ab0CF264DF99D83525e9E11c7e4db01558AE1b1", - "acount2": "0xF386573563C3a75dBbd269FCe9782620826dDAc2" + "account1": "0x8ab0CF264DF99D83525e9E11c7e4db01558AE1b1", + "account2": "0xF386573563C3a75dBbd269FCe9782620826dDAc2" } \ No newline at end of file diff --git a/tests/e2e/jest/setup.js b/tests/e2e/jest/setup.js index 6b62ec8c4..f2ab95742 100644 --- a/tests/e2e/jest/setup.js +++ b/tests/e2e/jest/setup.js @@ -7,37 +7,49 @@ import createTransactionData from "../src/create_test_data/createTestDataManage" export const MetaMaskOptions = { metaMaskVersion: RECOMMENDED_METAMASK_VERSION, automation: "puppeteer", - headless: process.env.HEADLESS ? true : false, + // https://developer.chrome.com/articles/new-headless/ + headless: process.env.HEADLESS ? 'new' : false, metaMaskFlask: false, args: [ process.env.WSL ? "-no-sandbox" : "", ] }; + export default async function setup() { + console.log('Setup Start...'); + const { metaMask, browser } = await bootstrap(MetaMaskOptions); + let hostPage; try { await createTransactionData.resetTestTmpFiles(); await createTransactionData.createTransactionData(); // create test data + + process.env.PUPPETEER_WS_ENDPOINT = browser.wsEndpoint(); global.browser = browser; + global.metamask = metaMask; + + console.log(`browser.newPage()`); + hostPage = await browser.newPage().catch(err => { + console.error("browser.newPage() failed"); + throw err; + }); } catch (error) { // eslint-disable-next-line no-console console.log(error); throw error; } - - process.env.PUPPETEER_WS_ENDPOINT = browser.wsEndpoint(); - global.browser = browser; - global.metamask = metaMask; - const hostPage = await browser.newPage(); - await Config.getIns().initialize(); + console.log(`goto httpServer: ${Config.getIns().httpServer}`); await hostPage.goto(Config.getIns().httpServer); + const configParams = { networkName: Config.getIns().axonRpc.netWorkName, - rpc: Config.getIns().axonRpc.url, + rpc: process.env.AXON_RPC_URL || Config.getIns().axonRpc.url, chainId: "0x" + Config.getIns().axonRpc.chainId.toString(16), symbol: "AXON" } + console.log("MetaMask configs:", configParams); + // add custom network to a MetaMask await hostPage.evaluate((cfparams) => { window.ethereum.request({ @@ -58,7 +70,9 @@ export default async function setup() { }, configParams); await metaMask.acceptAddNetwork(false); await metaMask.switchNetwork("Axon"); - + await hostPage.bringToFront(); global.page = hostPage.page; + + console.log('Setup End.'); } diff --git a/tests/e2e/package.json b/tests/e2e/package.json index bd50de3cb..22205d91b 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,10 +1,13 @@ { "name": "axon-e2e-tests", "scripts": { - "test": "jest -i", + "http-server": "cd src && http-server &", + "test": "jest --runInBand", "test-single": "jest", + "posttest": "pkill -2 http-server || echo no http-server running", "lint": "eslint src/*.js ./*.js" }, + "license": "MIT", "dependencies": { "@chainsafe/dappeteer": "^5.2.0", "@ethereumjs/common": "^3.1.1", @@ -20,8 +23,7 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-sonarjs": "^0.19.0", "http-server": "^14.1.1", - "jest": "^29.5.0", - "wait-on": "^7.0.1" + "jest": "^29.5.0" }, "jest": { "preset": "@chainsafe/dappeteer", diff --git a/tests/e2e/src/create_test_data/createTestDataManage.js b/tests/e2e/src/create_test_data/createTestDataManage.js index 85eafad71..efe71ae7b 100644 --- a/tests/e2e/src/create_test_data/createTestDataManage.js +++ b/tests/e2e/src/create_test_data/createTestDataManage.js @@ -1,5 +1,5 @@ -import Web3 from "web3"; import fs from "fs"; +import Web3 from "web3"; import Config from "../../config"; import erc20 from "./ERC20.json"; @@ -116,7 +116,7 @@ const createTestDataMange = { }, async sendRawTestTx() { - const toAddress = Config.getIns().acount2; + const toAddress = Config.getIns().account2; const nonce = (await web3.eth.getTransactionCount(accountFrom.address)) + 1; const txObject = { nonce: web3.utils.toHex(nonce), diff --git a/tests/e2e/yarn.lock b/tests/e2e/yarn.lock index 6fe90656c..cfcbfe9be 100644 --- a/tests/e2e/yarn.lock +++ b/tests/e2e/yarn.lock @@ -798,18 +798,6 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.1" -"@hapi/hoek@^9.0.0": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" - integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" @@ -1216,23 +1204,6 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sinclair/typebox@^0.25.16": version "0.25.21" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" @@ -1638,14 +1609,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - b4a@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" @@ -2087,7 +2050,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3140,11 +3103,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== -follow-redirects@^1.14.9: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -3167,15 +3125,6 @@ form-data-encoder@1.7.1: resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -4386,17 +4335,6 @@ jest@^29.5.0: import-local "^3.0.2" jest-cli "^29.5.0" -joi@^17.7.0: - version "17.7.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3" - integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.0" - "@sideway/pinpoint" "^2.0.0" - js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -4580,7 +4518,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.14, lodash@^4.17.21: +lodash@^4.17.14: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4743,7 +4681,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.7: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== @@ -5591,13 +5529,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== - dependencies: - tslib "^2.1.0" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -6123,11 +6054,6 @@ tslib@^2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== -tslib@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6351,17 +6277,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -wait-on@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9" - integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog== - dependencies: - axios "^0.27.2" - joi "^17.7.0" - lodash "^4.17.21" - minimist "^1.2.7" - rxjs "^7.8.0" - walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"