From 4a27f13a5193f0af21864db1d9132da8f62f0c38 Mon Sep 17 00:00:00 2001 From: Honza Javorek Date: Thu, 25 Apr 2019 17:29:19 +0200 Subject: [PATCH] refactor: rewrite to Node.js --- .circleci/config.yml | 8 +-- .travis.example.yml | 7 --- Gemfile | 4 -- Gemfile.lock | 33 ---------- features/step_definitions/dredd_steps.rb | 42 ------------- features/support/env.rb | 11 ---- features/support/steps.js | 76 ++++++++++++++++++++++-- features/tcp_server.feature | 12 ++-- package-lock.json | 18 ++++-- package.json | 4 +- scripts/{prepare-test.js => test.js} | 11 +++- 11 files changed, 105 insertions(+), 121 deletions(-) delete mode 100644 .travis.example.yml delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 features/step_definitions/dredd_steps.rb delete mode 100644 features/support/env.rb rename scripts/{prepare-test.js => test.js} (76%) diff --git a/.circleci/config.yml b/.circleci/config.yml index dadc6e7..b8ab751 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: steps: - checkout - run: npm install - - run: npm run lint:features + - run: npm run lint test-with-python-hooks: docker: @@ -23,11 +23,7 @@ jobs: command: sudo pip install dredd_hooks - run: name: Test - command: | - node scripts/prepare-test.js - export PATH=$PATH:$(pwd)/node_modules/.bin - cd ./test - cucumber-js features + command: npm test workflows: version: 2 diff --git a/.travis.example.yml b/.travis.example.yml deleted file mode 100644 index 79c0a32..0000000 --- a/.travis.example.yml +++ /dev/null @@ -1,7 +0,0 @@ -before_install: - - nvm install node && nvm use node - - npm install -g dredd --no-optional - - bundle install - -script: - - bundle exec cucumber diff --git a/Gemfile b/Gemfile deleted file mode 100644 index a7988ae..0000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem "aruba", "0.6.2" -gem "cucumber", "~> 1.3.20" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index db9aaae..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,33 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - aruba (0.6.2) - childprocess (>= 0.3.6) - cucumber (>= 1.1.1) - rspec-expectations (>= 2.7.0) - builder (3.2.3) - childprocess (1.0.1) - rake (< 13.0) - cucumber (1.3.20) - builder (>= 2.1.2) - diff-lcs (>= 1.1.3) - gherkin (~> 2.12) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - diff-lcs (1.3) - gherkin (2.12.2) - multi_json (~> 1.3) - multi_json (1.13.1) - multi_test (0.1.2) - rake (12.3.2) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - -PLATFORMS - ruby - -DEPENDENCIES - aruba (= 0.6.2) - cucumber (~> 1.3.20) diff --git a/features/step_definitions/dredd_steps.rb b/features/step_definitions/dredd_steps.rb deleted file mode 100644 index 06d2526..0000000 --- a/features/step_definitions/dredd_steps.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'json' -require 'socket' - - -Given(/^I have "([^"]*)" command installed$/) do |command| - is_present = system("which #{ command} > /dev/null 2>&1") - raise "Command #{command} is not present in the system" if not is_present -end - -Given(/^server under test is running$/) do -end - -Then(/^It should start listening on localhost port "([^"]*)"$/) do |port| - @client = TCPSocket.new 'localhost', port - @client.close -end - -Given(/^I connect to the server$/) do - @client = TCPSocket.new 'localhost', 61321 -end - -When(/^I send a JSON message to the socket:$/) do |string| - @data_sent = string - @client.send @data_sent, 0 -end - -When(/^I send a newline character as a message delimiter to the socket$/) do - @client.send "\n", 0 -end - -Then(/^I should receive same response$/) do - sleep 1 - data_received = @client.readline - if JSON.parse(data_received) != JSON.parse(@data_sent) - @client.close! - raise "Data received:\n#{data_received}\nDoesn't match data sent: #{@data_sent}\n" - end -end - -Then(/^I should be able to gracefully disconnect$/) do - @client.close -end \ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb deleted file mode 100644 index 393593d..0000000 --- a/features/support/env.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'aruba/cucumber' - -Before do - puts "Killing server..." - system "for i in `ps axu | grep 'server.js' | awk '{print $2}'`; do kill -9 $i; done > /dev/null 2>&1" - puts "Killing handler..." - system "for i in `ps axu | grep 'dredd-hooks' | awk '{print $2}'`; do kill -9 $i; done > /dev/null 2>&1" - sleep 3 - - @aruba_timeout_seconds = 10 -end diff --git a/features/support/steps.js b/features/support/steps.js index 6955142..a0b9d0e 100644 --- a/features/support/steps.js +++ b/features/support/steps.js @@ -1,19 +1,25 @@ -const { expect } = require('chai'); -const fs = require('fs-extra'); const os = require('os'); const path = require('path'); -const which = require('which'); +const util = require('util'); const childProcess = require('child_process'); +const { expect } = require('chai'); +const fs = require('fs-extra'); +const net = require('net'); +const which = require('which'); +const kill = require('tree-kill'); const { Given, When, Then, Before, After } = require('cucumber'); Before(function () { this.dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dredd-hooks-template-')); this.env = { ...process.env }; + this.commands = []; + this.dataSent = ''; }); -After(function () { +After(async function () { fs.remove(this.dir); + await util.promisify(kill)(this.proc.pid); }); @@ -38,11 +44,69 @@ When(/^I run `([^`]+)`$/, function (command) { }); }); +When(/^I run `([^`]+)` interactively$/, function (command) { + this.proc = childProcess.spawn(command, [], { + shell: true, + cwd: this.dir, + env: this.env, + }); +}); + +When('I wait for output to contain {string}', function (output, callback) { + const proc = this.proc; + + function read(data) { + if (data.toString().includes(output)) { + proc.stdout.removeListener('data', read); + proc.stderr.removeListener('data', read); + callback(); + } + } + + proc.stdout.on('data', read); + proc.stderr.on('data', read); +}); -Then('the exit status should be {int}', function (number) { - expect(this.proc.status).to.equal(parseInt(number, 10)); +When('I connect to the server', async function () { + this.socket = new net.Socket(); + const connect = util.promisify(this.socket.connect.bind(this.socket)); + await connect(61321, '127.0.0.1'); +}); + +When('I send a JSON message to the socket:', function (message) { + this.socket.write(message); + this.dataSent += message; +}); + +When('I send a newline character as a message delimiter to the socket', function () { + this.socket.write('\n'); +}); + + +Then('the exit status should be {int}', function (status) { + expect(this.proc.status).to.equal(status); }); Then('the output should contain:', function (output) { expect(this.proc.stdout.toString() + this.proc.stderr.toString()).to.contain(output); }); + +Then('it should start listening on localhost port {int}', async function (port) { + this.socket = new net.Socket(); + const connect = util.promisify(this.socket.connect.bind(this.socket)); + await connect(port, '127.0.0.1'); // throws if there's an issue + this.socket.end(); +}); + +Then('I should receive the same response', function (callback) { + this.socket.on('data', (data) => { + const dataReceived = JSON.parse(data.toString()); + const dataSent = JSON.parse(this.dataSent); + expect(dataReceived).to.deep.equal(dataSent); + callback(); + }); +}); + +Then('I should be able to gracefully disconnect', function () { + this.socket.end(); +}); diff --git a/features/tcp_server.feature b/features/tcp_server.feature index 2de1102..c13844d 100644 --- a/features/tcp_server.feature +++ b/features/tcp_server.feature @@ -3,7 +3,7 @@ Feature: TCP server and messages Scenario: TCP server When I run `dredd-hooks-{{mylanguage}}` interactively And I wait for output to contain "Starting" - Then It should start listening on localhost port "61321" + Then it should start listening on localhost port 61321 Scenario: Message exchange for event beforeEach Given I run `dredd-hooks-{{mylanguage}}` interactively @@ -14,7 +14,7 @@ Scenario: Message exchange for event beforeEach {"event": "beforeEach", "uuid": "1234-abcd", "data": {"key":"value"}} """ And I send a newline character as a message delimiter to the socket - Then I should receive same response + Then I should receive the same response And I should be able to gracefully disconnect Scenario: Message exchange for event beforeEachValidation @@ -26,7 +26,7 @@ Scenario: Message exchange for event beforeEachValidation {"event": "beforeEachValidation", "uuid": "2234-abcd", "data": {"key":"value"}} """ And I send a newline character as a message delimiter to the socket - Then I should receive same response + Then I should receive the same response And I should be able to gracefully disconnect Scenario: Message exchange for event afterEach @@ -38,7 +38,7 @@ Scenario: Message exchange for event afterEach {"event": "afterEach", "uuid": "3234-abcd", "data": {"key":"value"}} """ And I send a newline character as a message delimiter to the socket - Then I should receive same response + Then I should receive the same response And I should be able to gracefully disconnect Scenario: Message exchange for event beforeAll @@ -50,7 +50,7 @@ Scenario: Message exchange for event beforeAll {"event": "beforeAll", "uuid": "4234-abcd", "data": {"key":"value"}} """ And I send a newline character as a message delimiter to the socket - Then I should receive same response + Then I should receive the same response And I should be able to gracefully disconnect Scenario: Message exchange for event afterAll @@ -62,5 +62,5 @@ Scenario: Message exchange for event afterAll {"event": "afterAll", "uuid": "5234-abcd", "data": {"key":"value"}} """ And I send a newline character as a message delimiter to the socket - Then I should receive same response + Then I should receive the same response And I should be able to gracefully disconnect diff --git a/package-lock.json b/package-lock.json index 41078a1..d7da0d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -569,12 +569,14 @@ "dependencies": { "deckardcain": { "version": "0.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7kp5y1Zv/ej5aKoUl8Hbe56NQK3/hqBM70iyaOMyLJDd1D+UZTtLDmDaWLHFaoRCIeitt3Py2Tu4BNuYDY4zQw==", "dev": true }, "drafter": { "version": "2.0.0-pre.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-6iiQwW7vZy5WDtWtg77JnPYyUn02DoaOyApLk1FkQWmuG9zRO44skWIfvRqe6CSoj7lI8s1h8HIvdz4XsEsZlA==", "dev": true, "requires": { "drafter.js": "^3.0.0-pre.2" @@ -582,12 +584,14 @@ }, "drafter.js": { "version": "3.0.0-pre.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-w3AwtCWvrhxs2oYAwA5zXTdRH0dvIrylUjfI4LOlhI4TOND1mss8f3B4YFd3SQOP19KbT5VaB7AcirWcXjB8ew==", "dev": true }, "fury-adapter-apib-parser": { "version": "0.13.0-beta", - "bundled": true, + "resolved": false, + "integrity": "sha512-+o2J4fdSCL0uhBSOAYSkHHfb1GWzoAPOrKEas/TX1luG5Lt9t/oNZsSVbUiHj8QuYwIRZAO5Y8LnYYubHNcRzw==", "dev": true, "requires": { "deckardcain": "^0.4.0", @@ -2274,6 +2278,12 @@ } } }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", diff --git a/package.json b/package.json index 8516bb9..74928fb 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "fs-extra": "7.0.1", "gherkin-lint": "3.0.3", "glob": "7.1.3", + "tree-kill": "1.2.1", "which": "1.3.1" }, "scripts": { - "lint:features": "gherkin-lint features/" + "test": "node scripts/test.js", + "lint": "gherkin-lint features/" }, "repository": { "type": "git", diff --git a/scripts/prepare-test.js b/scripts/test.js similarity index 76% rename from scripts/prepare-test.js rename to scripts/test.js index 805b7c6..66c8d8d 100644 --- a/scripts/prepare-test.js +++ b/scripts/test.js @@ -1,9 +1,10 @@ const fs = require('fs-extra'); const path = require('path'); const glob = require('glob'); +const { spawnSync } = require('child_process'); -const PROJECT_DIR = path.join(__dirname, '..') +const PROJECT_DIR = path.join(__dirname, '..'); const TEST_DIR = path.join(PROJECT_DIR, 'test'); @@ -45,3 +46,11 @@ glob.sync(path.join(TEST_DIR, '**/*.feature')).forEach((featurePath) => { const modifiedContent = uncommentPythonCodeBlocks(replacePlaceholders(content)); fs.writeFileSync(featurePath, modifiedContent, { encoding: 'utf-8' }); }) + +const binDir = path.join(PROJECT_DIR, 'node_modules', '.bin'); +const featuresDir = path.join(TEST_DIR, 'features'); + +const PATH = process.env.PATH.split(path.delimiter).concat([binDir]).join(path.delimiter); +const env = { ...process.env, PATH }; + +spawnSync('cucumber-js', [featuresDir], { cwd: TEST_DIR, env, stdio: 'inherit' });