Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding semantic and model validator to CI #910

Merged
merged 3 commits into from
Feb 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "4"
- "6"
services:
- docker
env:
Expand All @@ -11,13 +11,21 @@ env:
- MODE=ruby
- MODE=linter PR_ONLY=false
- MODE=linter PR_ONLY=true
- MODE=semantic PR_ONLY=false
- MODE=semantic PR_ONLY=true
- MODE=model PR_ONLY=false
- MODE=model PR_ONLY=true
global:
secure: IwPVExGJQfL6QpTd6Utv3R/qBz7yApgMspzMLSkrq9cMP6t+NHk1vYg0n2evcWaq2rqNZE9xBMfRZi7Yy/RCUT++qALE89xlNvZ/A94PaDUiK3tYYNj1XxsgRauCHshXz6smmLwQMpTKsEl6lyjXeXpwQMDNRehhTu0Hg2tE7ZikfQVb+jjRNcP41RfzzGQyxEhd3fxF2G1G21WXH0cXtZssOd1MYcXhmXk8dR3DyEEMbPzDV1Bk3auXOvbqS79bnZ/Pi4p/7P/aTOm8O2ACKM0XAvww95vAQ0LcPzKnNe3sNjFVAssBiZbIk0Zs30wULBphlTxVoufHKSZuTf+QEBTKpH99v/+SBDDPu9+0q2esq7TKgf8bvzkeXjh54fECdJUqEo9E2gW08+RQxWCqryMJouOPcY2OMs4lZwSMOXQY68a/CYVRWFaFg5s6jntC8sLHtDxV0qem3xyjc+852v8rUfkyvMhOBoZJjWmnqYVqdamHOOfrzjc7AzPUEzSeiN6OPPVli+SzwLHdip0GxdK46pAISCOcbdyYn11VvTIn1QosE66eWhF6SViVH6lNWgSfVTpcQ2zq/qSKh0/zpwn82Ys+wKDOf3EwQAanndgk26npi7Ik4nIuexZ/TE56rQ7qjZqmHoxFz7QMeTZDiVmLFxtR19cTT3GLxDz8nBQ=
matrix:
fast_finish: true
allow_failures:
- env: MODE=linter PR_ONLY=false
- env: MODE=linter PR_ONLY=true
- env: MODE=semantic PR_ONLY=false
- env: MODE=semantic PR_ONLY=true
- env: MODE=model PR_ONLY=false
- env: MODE=model PR_ONLY=true
before_install:
- docker pull lmazuel/swagger-to-sdk
- python -c "import os; print('\n'.join(v for v in os.environ.keys() if v.startswith('TRAVIS')))" > /tmp/env_file
Expand All @@ -38,5 +46,7 @@ script:
- if [[ $MODE == 'python' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-python --pr-repo-id Azure/azure-sdk-for-python -o master -v; fi
- if [[ $MODE == 'node' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-node --pr-repo-id Azure/azure-sdk-for-node -o master -v; fi
- if [[ $MODE == 'ruby' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-ruby --pr-repo-id Azure/azure-sdk-for-ruby -o master -v; fi
- if [[ $MODE == 'syntax' ]]; then npm test -- test/test.js; fi
- if [[ $MODE == 'syntax' ]]; then npm test -- test/syntax.js; fi
- if [[ $MODE == 'linter' ]]; then npm test -- test/linter.js; fi
- if [[ $MODE == 'semantic' ]]; then npm test -- test/semantic.js; fi
- if [[ $MODE == 'model' ]]; then npm test -- test/model.js; fi
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Microsoft
Copyright (c) 2017 Microsoft

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
"version": "0.1.0",
"description": "Tests for Azure REST API Specifications",
"license": "MIT",
"dependencies": {
"devDependencies": {
"mocha": "*",
"async": "^2.1.4",
"glob": "^5.0.14",
"json-schema-ref-parser": "^3.1.2",
"lodash": "^3.10.1",
"request": "^2.61.0",
"z-schema": "^3.16.1"
},
"devDependencies": {
"mocha": "*"
"z-schema": "^3.16.1",
"openapi-validation-tools": "azure/openapi-validation-tools#master"
},
"homepage": "https://github.com/azure/azure-rest-api-specs",
"repository": {
Expand All @@ -31,4 +30,4 @@
"scripts": {
"test": "mocha -t 500000"
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I am not sure what is the general rule for line ending in JavaScript world?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no rule as such.

55 changes: 7 additions & 48 deletions test/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,17 @@
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var glob = require('glob'),
path = require('path'),
_ = require('lodash');

var globPath, swaggers;
var execSync = require('child_process').execSync;
var isWindows = (process.platform.lastIndexOf('win') === 0);
var prOnly = undefined !== process.env['PR_ONLY'] ? process.env['PR_ONLY'] : 'false';
globPath = path.join(__dirname, '../', '/**/swagger/*.json');
swaggers = _(glob.sync(globPath));

/**
* Converts command to OS specific command by prepending `mono` for non-windows prOnlySwaggers
* @returns {string} clr command
*/
function clrCmd(cmd) {
return isWindows ? cmd : ('mono ' + cmd);
};

/**
* Retrieves list of swagger files to be processed for linting
* @returns {Array} list of files to be processed for linting
*/
function getFilesForLinter() {
if (prOnly === 'true') {
// TODO: Currently works for PR into master branch only
var cmd = 'git diff --name-only HEAD $(git merge-base HEAD master)';
let result;
try {
result = execSync(cmd, { encoding: 'utf8' });
console.log(result);
var swaggerFileInPR = result.split('\n').filter(function (item) {
return (item.match(/.*\/swagger\/*/ig) !== null);
});
console.log(`>>>> Number of swaggers found in this PR: ${swaggerFileInPR.length}`);
return swaggerFileInPR;
} catch (err) {
throw err;
}
} else {
// Return all the swagger files for linter processing
return swaggers;
}
}
var _ = require('lodash'),
execSync = require('child_process').execSync,
utils = require('./util/utils');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Thanks for refactoring the code into utils 👍


describe('AutoRest Linter validation:', function () {
var autoRestLocation = './AutoRest.*/tools/AutoRest.exe';
let swaggersToProcess = getFilesForLinter();
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should follow linter rules.', function (done) {
var cmd = clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger);
console.log(cmd);
it(swagger + ' should honor linter validation rules.', function (done) {
var cmd = utils.clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger);
console.log(`Executing: ${cmd}`);
let result;
try {
result = execSync(cmd, { encoding: 'utf8' });
Expand Down
19 changes: 19 additions & 0 deletions test/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var _ = require('lodash'),
utils = require('./util/utils'),
oav = require('openapi-validation-tools');

describe('Azure swagger model validation using x-ms-examples and examples in spec', function () {
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should have valid examples.', function (done) {
oav.validateExamples(swagger, null, false, 'error').catch(function (err) {
console.log(err);
});
done();
});
}).value();
});
19 changes: 19 additions & 0 deletions test/semantic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var _ = require('lodash'),
utils = require('./util/utils'),
oav = require('openapi-validation-tools');

describe('Azure swagger semantic validation:', function () {
let swaggersToProcess = utils.getFilesChangedInPR();
_(swaggersToProcess).each(function (swagger) {
it(swagger + ' should be semantically valid.', function (done) {
oav.validateSpec(swagger, false, 'error').catch(function (err) {
console.log(err);
});
done();
});
}).value();
});
142 changes: 142 additions & 0 deletions test/syntax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License in the project root for license information.

'use strict';
var assert = require("assert"),
fs = require('fs'),
path = require('path'),
_ = require('lodash'),
async = require('async'),
RefParser = require('json-schema-ref-parser'),
util = require('util'),
utils = require('./util/utils');

var context;


// Useful when debugging a test for a particular swagger.
// Just update the regex. That will return an array of filtered items.
// swaggers = swaggers.filter(function(item) {
// return (item.match(/.*arm-redis.*/ig) !== null);
// });


describe('Azure swagger schema validation:', function () {
before(function (done) {
utils.initializeValidator(function (err, result) {
if (err) {
done(err);
}
context = result;
done();
});
});

_(utils.swaggers).each(function (swagger) {
it(swagger + ' should be a valid Swagger document.', function (done) {
utils.parseJsonFromFile(swagger, function (err, parsedData) {
if (err) { done(err); }
if (parsedData.documents && util.isArray(parsedData.documents)) {
console.log(util.format('Skipping the test for \'%s\' document as it seems to be a composite swagger doc.', swagger));
done();
}
var valid = context.validator.validate(parsedData, context.extensionSwaggerSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
done();
});
});
}).value();

describe('Azure composite swagger schema validation:', function () {
_(utils.compositeSwaggers).each(function (compositeSwagger) {
it('composite: ' + compositeSwagger + ' should be a valid Composite Swagger document.', function (done) {
utils.parseJsonFromFile(compositeSwagger, function (err, parsedData) {
if (err) { done(err); }
var valid = context.validator.validate(parsedData, context.compositeSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation for Composite Swagger failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
var compositeSwaggerDir = path.dirname(compositeSwagger);
var messages = [];
if (parsedData.documents && parsedData.documents.length > 0) {
async.eachSeries(parsedData.documents, function (docUrl, loopCallback) {
//construct the absolue path if the item in the documents array is a relative path
if (!path.isAbsolute(docUrl) && !docUrl.startsWith('http')) {
docUrl = path.join(compositeSwaggerDir, docUrl);
}
//make a request if it is a url
if (docUrl.startsWith('http')) {
request({ url: docUrl, json: true }, function (error, response, responseBody) {
if (error) {
messages.push('An error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list. The error is ' + util.inspect(error, { depth: null }));
}
if (response.statusCode !== 200) {
messages.push('\'' + response.statusCode + '\': \'File Not Found\'- error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list.');
}
loopCallback();
});
} else {
//check whether the file exists
if (!fs.existsSync(docUrl)) {
messages.push('\'File Not Found\': error occurred while accessing the swagger doc ' +
docUrl + ' from the documents list on the host filesystem.');
}
loopCallback();
}
}, function (err) {
if (err) {
throw err;
}
if (messages.length > 0) {
throw new Error(JSON.stringify(messages));
}
done();
});
} else {
done();
}
});
});
}).value();
});

describe('Azure x-ms-example schema validation:', function () {
_(utils.examples).each(function (example) {
it('x-ms-examples: ' + example + ' should be a valid x-ms-example.', function (done) {
utils.parseJsonFromFile(example, function (err, parsedData) {
if (err) { done(err); }
var valid = context.validator.validate(parsedData, context.exampleSchema);
if (!valid) {
var error = context.validator.getLastErrors();
throw new Error("Schema validation failed: " + util.inspect(error, { depth: null }));
}
assert(valid === true);
done();
});
});
}).value();
});
});

describe('External file or url references ("$ref") in a swagger spec:', function () {
_(utils.swaggers).each(function (swagger) {
it(swagger + ' should be completely resolvable.', function (done) {
RefParser.bundle(swagger, function (bundleErr, bundleResult) {
if (bundleErr) {
var msg = swagger + ' has references that cannot be resolved. They are as follows: \n' + util.inspect(bundleErr.message, { depth: null });
console.log(msg);
throw new Error(msg);
}
done();
});
});
}).value();
});
Loading