├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── Stat-Output.md ├── implementations ├── php │ ├── .gitignore │ ├── LICENSE │ ├── composer.json │ ├── composer.lock │ ├── composer.phar │ ├── sample-producer.php │ └── src │ │ └── StructuredAcceptanceTest │ │ ├── StatFinding.php │ │ ├── StatFindingDetail.php │ │ ├── StatFindingFix.php │ │ ├── StatFindingLocation.php │ │ ├── StatOutput.php │ │ ├── StatPrinter.php │ │ ├── StatPrinterJson.php │ │ ├── StatPrinterPretty.php │ │ └── StatProcess.php └── ruby │ ├── .gitignore │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── Rakefile │ ├── lib │ ├── JSONable.rb │ ├── category.rb │ ├── detail.rb │ ├── duplicate_element_exception.rb │ ├── finding.rb │ ├── fix.rb │ ├── index_out_of_bound_exception.rb │ ├── location.rb │ ├── process.rb │ ├── repeatability.rb │ ├── structured-acceptance-test.rb │ └── type_exception.rb │ ├── structured-acceptance-test.gemspec │ └── test │ └── test_structured-acceptance-test.rb ├── statJsonSchema.json └── tools ├── atom-linter-example ├── .gitignore ├── README.md ├── img │ └── stat.gif ├── keymaps │ └── stat.json ├── lib │ └── stat.js ├── menus │ └── stat.json └── package.json ├── example-consumer └── stat-reader │ ├── Gemfile │ ├── README.md │ ├── stat-reader.rb │ └── version.rb ├── example-process ├── Gemfile ├── README.md ├── bin │ └── example-process ├── example-process.bash ├── example-process.gemspec └── example.json ├── junit-stat-converter ├── Gemfile ├── README.md ├── features │ ├── run_example_tests.feature │ └── steps │ │ ├── junit1.xml │ │ ├── junit2.xml │ │ └── steps.rb ├── junit-stat-converter.rb └── version.rb └── stat-validator ├── stat-validator.rb └── version.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [fulldecent] 4 | custom: ["https://www.paypal.me/fulldecent", "https://amazon.com/hz/wishlist/ls/EE78A23EEGQB"] 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 William Entriken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Structured Acceptance Test 2 | 3 | **Structured Acceptance Test ("STAT") is a simple and extensible standard for *acceptance testing processes*.** The *target* of the test can be any set of computer files, for example source code, images, audio files and documents. The *process* can be an automated computer program, manual execution of a test plan or even a committee review. The *outcome* of the *process* is *findings* and *recommendations* which can be used to improve the *target* and can indicate a `pass` or `fail` result. 4 | 5 | The [STAT Output Specification](Stat-Output.md) defines how a *process* can express the *outcome*, *findings* and *recommendations* 6 | 7 | # Who can use it? 8 | 9 | This standard is applicable for any automated or manual *process* that accepts computer files as input and can express opinions about them. Examples of such *processes* include: 10 | 11 | * Static code analyzers 12 | * Code linters 13 | * Code style checkers 14 | * Minification checkers 15 | * Other static analyzers 16 | * Image metadata checkers 17 | * Image compression checkers 18 | * Spelling checkers 19 | * Grammar checkers 20 | * Digital signature verification 21 | * Dynamic code analysis 22 | * Unit tests (logic tests) 23 | * Automated UI tests 24 | * Continuous integration tests 25 | * Volatile testing 26 | * Link checkers 27 | * Package manager version checkers (requirement to use latest upstream versions) 28 | * Deployment testing (dry run) 29 | * Manual testing 30 | * Manual inspection 31 | * Manual walkthroughs 32 | * Unstructured customer feedback 33 | * Committee vote 34 | 35 | The acceptance testing *outcome*, *findings* and *recommendations* can be used (*consumed*) by: 36 | 37 | * Interactive file editors (IDEs, text editors, word processors, image editors) 38 | * Command-line reporting tools (unit test reports) 39 | * Source code management / content management systems 40 | 41 | # Why should I use it? XKCD 927? 42 | 43 | **If your acceptance testing *process* uses a standardized output format then *consumers* can make better use of it.** 44 | 45 | Integrations are amazing. They allow `clang` compilers to show compile errors in your integrated development environment, they allow spelling errors to be underlined in your word processor and they show up as red flags when you review a pull request. But what if *all* of these validations can be shown *everywhere* they are relevant? Standardization allows this. 46 | 47 | This is the first widely-applicable standardization of its type so [XKCD 927](https://xkcd.com/927/) does not apply. 48 | 49 | Specific features of this specification include: 50 | 51 | * **It is simple**, a few lines of Ruby can translate `gcc`, `clang` or `aspell` output into the required format. 52 | * **The format is extensible**, any acceptance testing *process* can use this format. 53 | * **Validation output is streamable** and available to the reporting tool incrementally. 54 | * **Repeatability** is specified. 55 | 56 | # Project Status 57 | 58 | This standard is currently version 1.0.0. We follow [Semantic Versioning](http://semver.org/). 59 | 60 | - [x] Defines the input format to select computer files 61 | - [x] Defines the output format for the *outcome*, *findings* and *recommendations* 62 | - [x] Follows the [Google JSON Style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) 63 | - [x] Supports real-time, incremental reporting 64 | - [x] An example computer program is produced that supports STAT 65 | - [x] An example computer program is produced that reads and reports the above output (Issue #6) 66 | - [x] Useful computer program is produced (or transformed) to support STAT 67 | - [x] Compatibility is established with [Code Climate Engine specification](https://github.com/codeclimate/spec) 68 | 69 | # Copyright 70 | 71 | This specification is copyright 2016 William Entriken and is released under the MIT license. 72 | 73 | # Contributing 74 | 75 | Development of this specification happens on GitHub. Please use [issues](https://github.com/fulldecent/structured-acceptance-test/issues) and [pull requests](https://github.com/fulldecent/structured-acceptance-test/pulls) to help improve it. 76 | 77 | Please help to raise awareness by opening an issue with [your favorite static analysis tool](https://en.wikipedia.org/wiki/List_of_tools_for_static_code_analysis) to support this format. 78 | 79 | Please open an issue with your favorite integrated development environment or source code sharing tool to request interoperability with this format. 80 | 81 | ----- 82 | 83 | Please add your own projects below! 84 | 85 | Supporting acceptance testing programs: 86 | 87 | * [web-puc - Web package update checker](https://github.com/fulldecent/web-puc) 88 | * [Line ending linter](https://github.com/mcandre/lili) 89 | * [Column width linter](https://github.com/mcandre/cowl) 90 | * [TO-DO items linter](https://github.com/mcandre/gtdlint) 91 | * [Encoding linter](https://github.com/mcandre/enlint) 92 | * [Spell check linter](https://github.com/mcandre/aspelllint) 93 | * YOUR NAME HERE 94 | 95 | Supporting consumers: 96 | 97 | * Interactive file editors (IDEs, text editors, word processors, image editors) 98 | * YOUR NAME HERE 99 | * Command-line reporting tools (unit test reports) 100 | * YOUR NAME HERE 101 | * Source code management / content management systems 102 | * YOUR NAME HERE 103 | -------------------------------------------------------------------------------- /Stat-Output.md: -------------------------------------------------------------------------------- 1 | # Structured Acceptance Test Output 2 | 3 | This document is part of the Structured Acceptance Test specification, please see versioning information and details at [the project main page](README.md). *Process* refers to a computer program that supports the Structured Acceptance Test standard. *Consumer* refers to something that uses the output of the *process*. 4 | 5 | The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMEND, MAY, and OPTIONAL in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). 6 | 7 | ## Valid output examples 8 | 9 | ### The simplest valid example 10 | 11 | ```json 12 | { 13 | "statVersion": "1.0.0", 14 | "process": { 15 | "name": "Vowel grep" 16 | } 17 | } 18 | ``` 19 | 20 | This example identifies an acceptance test named "Vowel grep" and does not express any findings. A `pass` outcome is implied. 21 | 22 | ### Full-featured example 23 | 24 | ```json 25 | { 26 | "statVersion": "1.0.0", 27 | "process": { 28 | "name": "Vowel grep", 29 | "version": "0.1.0", 30 | "description": "Ensures that no a's, e's, i's, o's or u's are found", 31 | "maintainer": "William Entriken", 32 | "email": "github.com@phor.net", 33 | "website": "https://github.com/fulldecent/structured-acceptance-test/tree/master/Examples", 34 | "repeatability": "Associative" 35 | }, 36 | "findings": [ 37 | { 38 | "failure": true, 39 | "rule": "Vowel used", 40 | "description": "Letter a was used", 41 | "detail": { 42 | "body": "Don't use `a` because only consonants are allowed." 43 | }, 44 | "categories": [ 45 | "Style" 46 | ], 47 | "location": { 48 | "path": "code\/hello.txt", 49 | "beginLine": 1, 50 | "beginColumn": 2, 51 | "endLine": 1, 52 | "endColumn": 2 53 | }, 54 | "timeToFix": 60, 55 | "recommendation": "Replace this with an X", 56 | "fixes": [ 57 | { 58 | "location": { 59 | "path": "code\/hello.txt", 60 | "beginLine": 1, 61 | "beginColumn": 2, 62 | "endLine": 1, 63 | "endColumn": 2 64 | }, 65 | "newText": "X" 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ``` 72 | 73 | This example shows the partial output of a *process* that disallows vowels being applied to a file containing "hello world". 74 | 75 | ## Full specification 76 | 77 | The output is a [JSON-formatted](http://www.json.org/) object containing a **Process** object and zero or more **Finding** objects. 78 | 79 | ```json 80 | { 81 | "statVersion": "1.0.0", 82 | "process": Process, 83 | "findings": [Finding] 84 | } 85 | ``` 86 | 87 | * `statVersion` — **Required** — The version of Structured Acceptance Test against which this output is valid 88 | * `process` — **Required** — Identification of the *process* which is doing testing 89 | * `findings` — **Optional** — A set of observations reported by the *process*, or `[]` if not specified 90 | 91 | INCREMENTAL OUTPUT: Each finding SHOULD be delimited by a new line. A computer program with STAT-compliant output SHOULD immediately output each finding as available (e.g. use `flush()`). This allows the `consumer` to begin processing the results right away. 92 | 93 | A *consumer* should ignore the entire STAT Output if it is not valid JSON. This may occur, for example, if the *process* was interrupted or had an internal error. 94 | 95 | A STAT-compliant computer program MUST produce STAT-compliant output on STDOUT and MUST exit with a return status of zero if no *failure* *findings* were reported, or non-zero otherwise. 96 | 97 | ### Process 98 | 99 | ```json 100 | { 101 | "name": String, 102 | "version": String, 103 | "description": String, 104 | "maintainer": String, 105 | "email": String, 106 | "website": String, 107 | "repeatability": Repeatability 108 | } 109 | ``` 110 | 111 | * `name` — **Required** — Identification of the acceptance test *process* 112 | * For automated tools, this MUST be the name of the tool 113 | * For manual review, this SHOULD refer to the name of the test and validation procedure performed 114 | * `version` — **Optional** — The version of the acceptance test *process* 115 | * `description` — **Optional** — A brief explanation of the acceptance test *process* 116 | * `maintainer` — **Optional** — The name of a person responsible for the acceptance test *process* 117 | * `email` — **Optional** — Contact email address for the maintainer 118 | * `website` — **Optional** — Contact website for the acceptance test *process* 119 | * `repeatability` — **Optional** — A guarantee of whether the same validation output can be expected in future validation, see notes below 120 | 121 | **CODE CLIMATE NOTE: everything is required except repeatability** 122 | 123 | #### Repeatability 124 | 125 | Repeatability is a guarantee that the same output MUST occur if the test is applied again to the same, or a similar set of targets. Because validation MAY be expensive, this will allow certain validations to be skipped in the future. 126 | 127 | A *consumer* MUST NOT consider a repeatability guarantee from one validation `name` and `version` to be valid with another. 128 | 129 | Repeatability MUST be one of the following strings: 130 | 131 | * `Volatile` — Findings MAY change when repeating validation on the identical targets. Example: a web link checker, or package manager version checker. **This is the implicit default if not specified.** 132 | * `Repeatable` — Findings MUST be identical if the program is run again with the same inputs. ("Inputs" is not specified by this standard.) 133 | * `Associative` — Findings for targets [a, b] MUST equal the union of findings for [a] and [b] -- in other words, if only one file in changed, only that file need be tested. 134 | 135 | Note: `Associative` is the strongest and most useful guarantee. 136 | 137 | ### Finding 138 | 139 | ```json 140 | { 141 | "failure": Bool, 142 | "rule": String, 143 | "description": String, 144 | "detail": Detail, 145 | "categories": [Category], 146 | "location": Location, 147 | "timeToFix": Number, 148 | "recommendation": String, 149 | "fixes": [Fix] 150 | } 151 | ``` 152 | 153 | * `failure` — **Required** — `true` if this finding causes the output to be `fail`; `false` otherwise 154 | * `rule` — **Required** — A succinct name which describes the rule which applies to this finding 155 | * `description` — **Required** — An explanation of the finding 156 | * `detail` — **Optional** — See *Detail* below 157 | * `categories` — **Optional** — See *Category* below 158 | * `location` — **Optional** — See *Location* below 159 | * `timeToFix` — **Optional** — The estimated amount of time it would take a knowledgable human to fix this problem, in seconds 160 | * `recommendation` — **Optional** — Explanation of an act, using imperative language (eg. "Add a semicolon", "Use the passive voice"), which would fix this finding 161 | * `fixes` — **Optional** — See *Fix* below 162 | 163 | **CODE CLIMATE NOTE: categories and location are required** 164 | 165 | *File progress* MAY be reported by setting `rule` to "`Progress`" and setting `description` to `done`. This allows the *process* to communicate each file that was considered and the overall status. 166 | 167 | #### Detail 168 | 169 | ```json 170 | { 171 | "body": String, 172 | "trace": [Location] 173 | } 174 | ``` 175 | 176 | * `body` — **Required** — A markdown-formatted, detailed explanation the finding, which may include links to further information 177 | * `trace` — **Optional** — An ordered list of `Location`s which provide context to the finding, with the first being the most closely related 178 | 179 | #### Category 180 | 181 | Category is a string that classifies the finding into a taxonomy. The taxonomy is not yet defined. 182 | 183 | **CODE CLIMATE NOTE: Only the following categories are allowed and they have the meaning shown below.** 184 | 185 | * `Bug Risk` — the meaning is likely to not be what the author intended 186 | * Code example: `if (a = 5)`, the author may have meant `==` 187 | * Non-code example: `I like there music`, the author probably meant `their` 188 | * `Clarity` — the meaning is unclear 189 | * Code example: `a = ++x--` 190 | * Non-code example: `I, to see a friend, am going town.` 191 | * `Compatibility` — the meaning has changed and is no longer valid 192 | * Code example: using `UIAlertView` in iOS 9 193 | * Non-code example: an Excel XLSX file, if only XLS is allowed 194 | * `Complexity` — the meaning should be broken into smaller pieces 195 | * Code example: a four-page long function 196 | * Non-code example: a flowchart with lines much longer than they must be 197 | * `Duplication` — unnecessary duplication was found 198 | * Code example: something that violates DRY principle 199 | * Non-code example: two images in a directory are identical 200 | * `Performance` — an inefficient approach was used 201 | * Code example: `for (i=0; i<1000000; count+=1)` 202 | * Non-code example: *I can't think of one* 203 | * `Security` — a situation may allow access to something that should be allowed 204 | * Code example: `echo $_POST['name']` 205 | * Non-code example: `... or you can use _NSAKEY to sign the binary` 206 | * `Style` — the style could be improved 207 | * Code example: `if ((((((a == (((5)))))))))` 208 | * Non-code example: an image uses colors inconsistent with style guide 209 | 210 | #### Location 211 | 212 | ```json 213 | { 214 | "path": String, 215 | "beginLine": Number, 216 | "beginColumn": Number, 217 | "endLine": Number, 218 | "endColumn": Number 219 | } 220 | ``` 221 | 222 | * `path` — **Required** — The file path to which this finding applies 223 | * `beginLine` — **Optional** — The first line of text affected (numbering starts with one), defaults to one 224 | * `beginColumn` — **Optional** — The first column of text affected (numbering starts with one), defaults to one 225 | * `endColumn` — **Optional** — The last column of text affected (numbering starts with one), defaults to the last 226 | * `endLine` — **Optional** — The last line of text affected (numbering starts with one), defaults to the last 227 | 228 | Note: if `beginLine` and `endLine` are both `1`, then `beginColumn` and `endColumn` SHALL represent byte offsets (still one-based). For example, a WAVE sound file containing `riff` at the beginning (it should be `RIFF`) would be identified by the location: 229 | 230 | {"path":file,"beginLine":1,"beginColumn":1,"endLine":1,"endColumn":4} 231 | 232 | Notes regarding *findings* that omit *location*: 233 | 234 | * The *consumer* must consider the *finding* to apply to the entirety of all targets. 235 | * A valid example is a failed check for the existence of a file (note: a location may not refer to an non-existent file) 236 | * A *process* which guarantees `Associative` repeatability MUST NOT include findings that omit location. 237 | 238 | #### Fix 239 | 240 | A *fix* is a proposed way to implement a *recommendation*. If a fix is provided, then a recommendation MUST be provided. 241 | 242 | ```json 243 | { 244 | "location": Location, 245 | "newText": String 246 | } 247 | ``` 248 | 249 | * `location` — **Required** — Where the recommendation can be applied 250 | * `newText` — **Optional** — New text which may be spliced into the *location* 251 | 252 | ### Extensibility 253 | 254 | Each object in this specification may have extra keys introduced by the *process* with proprietary information. The report *consumer* MUST ignore such additional information it does not understand. The `failure` key is reserved and may not be used in extensions. 255 | -------------------------------------------------------------------------------- /implementations/php/.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /implementations/php/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 William Entriken 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /implementations/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fulldecent/structured-acceptance-test", 3 | "description": "An open format definition for static analysis tools", 4 | "homepage": "https://github.com/fulldecent/structured-acceptance-test", 5 | "license": "MIT", 6 | "keywords": ["static", "testing", "lint", "linter"], 7 | "version": "0.0.1", 8 | "authors": [ 9 | { 10 | "name": "William Entriken", 11 | "email": "github.com@phor.net", 12 | "homepage": "http://phor.net" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.5" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "4.*|5.*" 20 | }, 21 | "autoload": { 22 | "psr-0": { 23 | "ThinPdo": "src/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /implementations/php/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e79ebe5b6b1ad6422cd5b54b4d8e0a5b", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.0.5", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 21 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3,<8.0-DEV" 26 | }, 27 | "require-dev": { 28 | "athletic/athletic": "~0.1.8", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpunit/phpunit": "~4.0", 32 | "squizlabs/php_codesniffer": "~2.0" 33 | }, 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.0.x-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Marco Pivetta", 52 | "email": "ocramius@gmail.com", 53 | "homepage": "http://ocramius.github.com/" 54 | } 55 | ], 56 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 57 | "homepage": "https://github.com/doctrine/instantiator", 58 | "keywords": [ 59 | "constructor", 60 | "instantiate" 61 | ], 62 | "time": "2015-06-14T21:17:01+00:00" 63 | }, 64 | { 65 | "name": "myclabs/deep-copy", 66 | "version": "1.6.0", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/myclabs/DeepCopy.git", 70 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", 75 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "php": ">=5.4.0" 80 | }, 81 | "require-dev": { 82 | "doctrine/collections": "1.*", 83 | "phpunit/phpunit": "~4.1" 84 | }, 85 | "type": "library", 86 | "autoload": { 87 | "psr-4": { 88 | "DeepCopy\\": "src/DeepCopy/" 89 | } 90 | }, 91 | "notification-url": "https://packagist.org/downloads/", 92 | "license": [ 93 | "MIT" 94 | ], 95 | "description": "Create deep copies (clones) of your objects", 96 | "homepage": "https://github.com/myclabs/DeepCopy", 97 | "keywords": [ 98 | "clone", 99 | "copy", 100 | "duplicate", 101 | "object", 102 | "object graph" 103 | ], 104 | "time": "2017-01-26T22:05:40+00:00" 105 | }, 106 | { 107 | "name": "phpdocumentor/reflection-common", 108 | "version": "1.0", 109 | "source": { 110 | "type": "git", 111 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 112 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" 113 | }, 114 | "dist": { 115 | "type": "zip", 116 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", 117 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", 118 | "shasum": "" 119 | }, 120 | "require": { 121 | "php": ">=5.5" 122 | }, 123 | "require-dev": { 124 | "phpunit/phpunit": "^4.6" 125 | }, 126 | "type": "library", 127 | "extra": { 128 | "branch-alias": { 129 | "dev-master": "1.0.x-dev" 130 | } 131 | }, 132 | "autoload": { 133 | "psr-4": { 134 | "phpDocumentor\\Reflection\\": [ 135 | "src" 136 | ] 137 | } 138 | }, 139 | "notification-url": "https://packagist.org/downloads/", 140 | "license": [ 141 | "MIT" 142 | ], 143 | "authors": [ 144 | { 145 | "name": "Jaap van Otterdijk", 146 | "email": "opensource@ijaap.nl" 147 | } 148 | ], 149 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 150 | "homepage": "http://www.phpdoc.org", 151 | "keywords": [ 152 | "FQSEN", 153 | "phpDocumentor", 154 | "phpdoc", 155 | "reflection", 156 | "static analysis" 157 | ], 158 | "time": "2015-12-27T11:43:31+00:00" 159 | }, 160 | { 161 | "name": "phpdocumentor/reflection-docblock", 162 | "version": "3.1.1", 163 | "source": { 164 | "type": "git", 165 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 166 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" 167 | }, 168 | "dist": { 169 | "type": "zip", 170 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", 171 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", 172 | "shasum": "" 173 | }, 174 | "require": { 175 | "php": ">=5.5", 176 | "phpdocumentor/reflection-common": "^1.0@dev", 177 | "phpdocumentor/type-resolver": "^0.2.0", 178 | "webmozart/assert": "^1.0" 179 | }, 180 | "require-dev": { 181 | "mockery/mockery": "^0.9.4", 182 | "phpunit/phpunit": "^4.4" 183 | }, 184 | "type": "library", 185 | "autoload": { 186 | "psr-4": { 187 | "phpDocumentor\\Reflection\\": [ 188 | "src/" 189 | ] 190 | } 191 | }, 192 | "notification-url": "https://packagist.org/downloads/", 193 | "license": [ 194 | "MIT" 195 | ], 196 | "authors": [ 197 | { 198 | "name": "Mike van Riel", 199 | "email": "me@mikevanriel.com" 200 | } 201 | ], 202 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 203 | "time": "2016-09-30T07:12:33+00:00" 204 | }, 205 | { 206 | "name": "phpdocumentor/type-resolver", 207 | "version": "0.2.1", 208 | "source": { 209 | "type": "git", 210 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 211 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" 212 | }, 213 | "dist": { 214 | "type": "zip", 215 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", 216 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", 217 | "shasum": "" 218 | }, 219 | "require": { 220 | "php": ">=5.5", 221 | "phpdocumentor/reflection-common": "^1.0" 222 | }, 223 | "require-dev": { 224 | "mockery/mockery": "^0.9.4", 225 | "phpunit/phpunit": "^5.2||^4.8.24" 226 | }, 227 | "type": "library", 228 | "extra": { 229 | "branch-alias": { 230 | "dev-master": "1.0.x-dev" 231 | } 232 | }, 233 | "autoload": { 234 | "psr-4": { 235 | "phpDocumentor\\Reflection\\": [ 236 | "src/" 237 | ] 238 | } 239 | }, 240 | "notification-url": "https://packagist.org/downloads/", 241 | "license": [ 242 | "MIT" 243 | ], 244 | "authors": [ 245 | { 246 | "name": "Mike van Riel", 247 | "email": "me@mikevanriel.com" 248 | } 249 | ], 250 | "time": "2016-11-25T06:54:22+00:00" 251 | }, 252 | { 253 | "name": "phpspec/prophecy", 254 | "version": "v1.7.0", 255 | "source": { 256 | "type": "git", 257 | "url": "https://github.com/phpspec/prophecy.git", 258 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" 259 | }, 260 | "dist": { 261 | "type": "zip", 262 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", 263 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", 264 | "shasum": "" 265 | }, 266 | "require": { 267 | "doctrine/instantiator": "^1.0.2", 268 | "php": "^5.3|^7.0", 269 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", 270 | "sebastian/comparator": "^1.1|^2.0", 271 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 272 | }, 273 | "require-dev": { 274 | "phpspec/phpspec": "^2.5|^3.2", 275 | "phpunit/phpunit": "^4.8 || ^5.6.5" 276 | }, 277 | "type": "library", 278 | "extra": { 279 | "branch-alias": { 280 | "dev-master": "1.6.x-dev" 281 | } 282 | }, 283 | "autoload": { 284 | "psr-0": { 285 | "Prophecy\\": "src/" 286 | } 287 | }, 288 | "notification-url": "https://packagist.org/downloads/", 289 | "license": [ 290 | "MIT" 291 | ], 292 | "authors": [ 293 | { 294 | "name": "Konstantin Kudryashov", 295 | "email": "ever.zet@gmail.com", 296 | "homepage": "http://everzet.com" 297 | }, 298 | { 299 | "name": "Marcello Duarte", 300 | "email": "marcello.duarte@gmail.com" 301 | } 302 | ], 303 | "description": "Highly opinionated mocking framework for PHP 5.3+", 304 | "homepage": "https://github.com/phpspec/prophecy", 305 | "keywords": [ 306 | "Double", 307 | "Dummy", 308 | "fake", 309 | "mock", 310 | "spy", 311 | "stub" 312 | ], 313 | "time": "2017-03-02T20:05:34+00:00" 314 | }, 315 | { 316 | "name": "phpunit/php-code-coverage", 317 | "version": "4.0.7", 318 | "source": { 319 | "type": "git", 320 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 321 | "reference": "09e2277d14ea467e5a984010f501343ef29ffc69" 322 | }, 323 | "dist": { 324 | "type": "zip", 325 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69", 326 | "reference": "09e2277d14ea467e5a984010f501343ef29ffc69", 327 | "shasum": "" 328 | }, 329 | "require": { 330 | "ext-dom": "*", 331 | "ext-xmlwriter": "*", 332 | "php": "^5.6 || ^7.0", 333 | "phpunit/php-file-iterator": "^1.3", 334 | "phpunit/php-text-template": "^1.2", 335 | "phpunit/php-token-stream": "^1.4.2 || ^2.0", 336 | "sebastian/code-unit-reverse-lookup": "^1.0", 337 | "sebastian/environment": "^1.3.2 || ^2.0", 338 | "sebastian/version": "^1.0 || ^2.0" 339 | }, 340 | "require-dev": { 341 | "ext-xdebug": "^2.1.4", 342 | "phpunit/phpunit": "^5.7" 343 | }, 344 | "suggest": { 345 | "ext-xdebug": "^2.5.1" 346 | }, 347 | "type": "library", 348 | "extra": { 349 | "branch-alias": { 350 | "dev-master": "4.0.x-dev" 351 | } 352 | }, 353 | "autoload": { 354 | "classmap": [ 355 | "src/" 356 | ] 357 | }, 358 | "notification-url": "https://packagist.org/downloads/", 359 | "license": [ 360 | "BSD-3-Clause" 361 | ], 362 | "authors": [ 363 | { 364 | "name": "Sebastian Bergmann", 365 | "email": "sb@sebastian-bergmann.de", 366 | "role": "lead" 367 | } 368 | ], 369 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 370 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 371 | "keywords": [ 372 | "coverage", 373 | "testing", 374 | "xunit" 375 | ], 376 | "time": "2017-03-01T09:12:17+00:00" 377 | }, 378 | { 379 | "name": "phpunit/php-file-iterator", 380 | "version": "1.4.2", 381 | "source": { 382 | "type": "git", 383 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 384 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" 385 | }, 386 | "dist": { 387 | "type": "zip", 388 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 389 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 390 | "shasum": "" 391 | }, 392 | "require": { 393 | "php": ">=5.3.3" 394 | }, 395 | "type": "library", 396 | "extra": { 397 | "branch-alias": { 398 | "dev-master": "1.4.x-dev" 399 | } 400 | }, 401 | "autoload": { 402 | "classmap": [ 403 | "src/" 404 | ] 405 | }, 406 | "notification-url": "https://packagist.org/downloads/", 407 | "license": [ 408 | "BSD-3-Clause" 409 | ], 410 | "authors": [ 411 | { 412 | "name": "Sebastian Bergmann", 413 | "email": "sb@sebastian-bergmann.de", 414 | "role": "lead" 415 | } 416 | ], 417 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 418 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 419 | "keywords": [ 420 | "filesystem", 421 | "iterator" 422 | ], 423 | "time": "2016-10-03T07:40:28+00:00" 424 | }, 425 | { 426 | "name": "phpunit/php-text-template", 427 | "version": "1.2.1", 428 | "source": { 429 | "type": "git", 430 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 431 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 432 | }, 433 | "dist": { 434 | "type": "zip", 435 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 436 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 437 | "shasum": "" 438 | }, 439 | "require": { 440 | "php": ">=5.3.3" 441 | }, 442 | "type": "library", 443 | "autoload": { 444 | "classmap": [ 445 | "src/" 446 | ] 447 | }, 448 | "notification-url": "https://packagist.org/downloads/", 449 | "license": [ 450 | "BSD-3-Clause" 451 | ], 452 | "authors": [ 453 | { 454 | "name": "Sebastian Bergmann", 455 | "email": "sebastian@phpunit.de", 456 | "role": "lead" 457 | } 458 | ], 459 | "description": "Simple template engine.", 460 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 461 | "keywords": [ 462 | "template" 463 | ], 464 | "time": "2015-06-21T13:50:34+00:00" 465 | }, 466 | { 467 | "name": "phpunit/php-timer", 468 | "version": "1.0.9", 469 | "source": { 470 | "type": "git", 471 | "url": "https://github.com/sebastianbergmann/php-timer.git", 472 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 473 | }, 474 | "dist": { 475 | "type": "zip", 476 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 477 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 478 | "shasum": "" 479 | }, 480 | "require": { 481 | "php": "^5.3.3 || ^7.0" 482 | }, 483 | "require-dev": { 484 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 485 | }, 486 | "type": "library", 487 | "extra": { 488 | "branch-alias": { 489 | "dev-master": "1.0-dev" 490 | } 491 | }, 492 | "autoload": { 493 | "classmap": [ 494 | "src/" 495 | ] 496 | }, 497 | "notification-url": "https://packagist.org/downloads/", 498 | "license": [ 499 | "BSD-3-Clause" 500 | ], 501 | "authors": [ 502 | { 503 | "name": "Sebastian Bergmann", 504 | "email": "sb@sebastian-bergmann.de", 505 | "role": "lead" 506 | } 507 | ], 508 | "description": "Utility class for timing", 509 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 510 | "keywords": [ 511 | "timer" 512 | ], 513 | "time": "2017-02-26T11:10:40+00:00" 514 | }, 515 | { 516 | "name": "phpunit/php-token-stream", 517 | "version": "1.4.11", 518 | "source": { 519 | "type": "git", 520 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 521 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" 522 | }, 523 | "dist": { 524 | "type": "zip", 525 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", 526 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", 527 | "shasum": "" 528 | }, 529 | "require": { 530 | "ext-tokenizer": "*", 531 | "php": ">=5.3.3" 532 | }, 533 | "require-dev": { 534 | "phpunit/phpunit": "~4.2" 535 | }, 536 | "type": "library", 537 | "extra": { 538 | "branch-alias": { 539 | "dev-master": "1.4-dev" 540 | } 541 | }, 542 | "autoload": { 543 | "classmap": [ 544 | "src/" 545 | ] 546 | }, 547 | "notification-url": "https://packagist.org/downloads/", 548 | "license": [ 549 | "BSD-3-Clause" 550 | ], 551 | "authors": [ 552 | { 553 | "name": "Sebastian Bergmann", 554 | "email": "sebastian@phpunit.de" 555 | } 556 | ], 557 | "description": "Wrapper around PHP's tokenizer extension.", 558 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 559 | "keywords": [ 560 | "tokenizer" 561 | ], 562 | "time": "2017-02-27T10:12:30+00:00" 563 | }, 564 | { 565 | "name": "phpunit/phpunit", 566 | "version": "5.7.16", 567 | "source": { 568 | "type": "git", 569 | "url": "https://github.com/sebastianbergmann/phpunit.git", 570 | "reference": "dafc78e2a7d12139b0e97078d1082326bd09363d" 571 | }, 572 | "dist": { 573 | "type": "zip", 574 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/dafc78e2a7d12139b0e97078d1082326bd09363d", 575 | "reference": "dafc78e2a7d12139b0e97078d1082326bd09363d", 576 | "shasum": "" 577 | }, 578 | "require": { 579 | "ext-dom": "*", 580 | "ext-json": "*", 581 | "ext-libxml": "*", 582 | "ext-mbstring": "*", 583 | "ext-xml": "*", 584 | "myclabs/deep-copy": "~1.3", 585 | "php": "^5.6 || ^7.0", 586 | "phpspec/prophecy": "^1.6.2", 587 | "phpunit/php-code-coverage": "^4.0.4", 588 | "phpunit/php-file-iterator": "~1.4", 589 | "phpunit/php-text-template": "~1.2", 590 | "phpunit/php-timer": "^1.0.6", 591 | "phpunit/phpunit-mock-objects": "^3.2", 592 | "sebastian/comparator": "^1.2.4", 593 | "sebastian/diff": "~1.2", 594 | "sebastian/environment": "^1.3.4 || ^2.0", 595 | "sebastian/exporter": "~2.0", 596 | "sebastian/global-state": "^1.1", 597 | "sebastian/object-enumerator": "~2.0", 598 | "sebastian/resource-operations": "~1.0", 599 | "sebastian/version": "~1.0.3|~2.0", 600 | "symfony/yaml": "~2.1|~3.0" 601 | }, 602 | "conflict": { 603 | "phpdocumentor/reflection-docblock": "3.0.2" 604 | }, 605 | "require-dev": { 606 | "ext-pdo": "*" 607 | }, 608 | "suggest": { 609 | "ext-xdebug": "*", 610 | "phpunit/php-invoker": "~1.1" 611 | }, 612 | "bin": [ 613 | "phpunit" 614 | ], 615 | "type": "library", 616 | "extra": { 617 | "branch-alias": { 618 | "dev-master": "5.7.x-dev" 619 | } 620 | }, 621 | "autoload": { 622 | "classmap": [ 623 | "src/" 624 | ] 625 | }, 626 | "notification-url": "https://packagist.org/downloads/", 627 | "license": [ 628 | "BSD-3-Clause" 629 | ], 630 | "authors": [ 631 | { 632 | "name": "Sebastian Bergmann", 633 | "email": "sebastian@phpunit.de", 634 | "role": "lead" 635 | } 636 | ], 637 | "description": "The PHP Unit Testing framework.", 638 | "homepage": "https://phpunit.de/", 639 | "keywords": [ 640 | "phpunit", 641 | "testing", 642 | "xunit" 643 | ], 644 | "time": "2017-03-15T13:02:34+00:00" 645 | }, 646 | { 647 | "name": "phpunit/phpunit-mock-objects", 648 | "version": "3.4.3", 649 | "source": { 650 | "type": "git", 651 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 652 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" 653 | }, 654 | "dist": { 655 | "type": "zip", 656 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", 657 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", 658 | "shasum": "" 659 | }, 660 | "require": { 661 | "doctrine/instantiator": "^1.0.2", 662 | "php": "^5.6 || ^7.0", 663 | "phpunit/php-text-template": "^1.2", 664 | "sebastian/exporter": "^1.2 || ^2.0" 665 | }, 666 | "conflict": { 667 | "phpunit/phpunit": "<5.4.0" 668 | }, 669 | "require-dev": { 670 | "phpunit/phpunit": "^5.4" 671 | }, 672 | "suggest": { 673 | "ext-soap": "*" 674 | }, 675 | "type": "library", 676 | "extra": { 677 | "branch-alias": { 678 | "dev-master": "3.2.x-dev" 679 | } 680 | }, 681 | "autoload": { 682 | "classmap": [ 683 | "src/" 684 | ] 685 | }, 686 | "notification-url": "https://packagist.org/downloads/", 687 | "license": [ 688 | "BSD-3-Clause" 689 | ], 690 | "authors": [ 691 | { 692 | "name": "Sebastian Bergmann", 693 | "email": "sb@sebastian-bergmann.de", 694 | "role": "lead" 695 | } 696 | ], 697 | "description": "Mock Object library for PHPUnit", 698 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 699 | "keywords": [ 700 | "mock", 701 | "xunit" 702 | ], 703 | "time": "2016-12-08T20:27:08+00:00" 704 | }, 705 | { 706 | "name": "sebastian/code-unit-reverse-lookup", 707 | "version": "1.0.1", 708 | "source": { 709 | "type": "git", 710 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 711 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 712 | }, 713 | "dist": { 714 | "type": "zip", 715 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 716 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 717 | "shasum": "" 718 | }, 719 | "require": { 720 | "php": "^5.6 || ^7.0" 721 | }, 722 | "require-dev": { 723 | "phpunit/phpunit": "^5.7 || ^6.0" 724 | }, 725 | "type": "library", 726 | "extra": { 727 | "branch-alias": { 728 | "dev-master": "1.0.x-dev" 729 | } 730 | }, 731 | "autoload": { 732 | "classmap": [ 733 | "src/" 734 | ] 735 | }, 736 | "notification-url": "https://packagist.org/downloads/", 737 | "license": [ 738 | "BSD-3-Clause" 739 | ], 740 | "authors": [ 741 | { 742 | "name": "Sebastian Bergmann", 743 | "email": "sebastian@phpunit.de" 744 | } 745 | ], 746 | "description": "Looks up which function or method a line of code belongs to", 747 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 748 | "time": "2017-03-04T06:30:41+00:00" 749 | }, 750 | { 751 | "name": "sebastian/comparator", 752 | "version": "1.2.4", 753 | "source": { 754 | "type": "git", 755 | "url": "https://github.com/sebastianbergmann/comparator.git", 756 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 757 | }, 758 | "dist": { 759 | "type": "zip", 760 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 761 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 762 | "shasum": "" 763 | }, 764 | "require": { 765 | "php": ">=5.3.3", 766 | "sebastian/diff": "~1.2", 767 | "sebastian/exporter": "~1.2 || ~2.0" 768 | }, 769 | "require-dev": { 770 | "phpunit/phpunit": "~4.4" 771 | }, 772 | "type": "library", 773 | "extra": { 774 | "branch-alias": { 775 | "dev-master": "1.2.x-dev" 776 | } 777 | }, 778 | "autoload": { 779 | "classmap": [ 780 | "src/" 781 | ] 782 | }, 783 | "notification-url": "https://packagist.org/downloads/", 784 | "license": [ 785 | "BSD-3-Clause" 786 | ], 787 | "authors": [ 788 | { 789 | "name": "Jeff Welch", 790 | "email": "whatthejeff@gmail.com" 791 | }, 792 | { 793 | "name": "Volker Dusch", 794 | "email": "github@wallbash.com" 795 | }, 796 | { 797 | "name": "Bernhard Schussek", 798 | "email": "bschussek@2bepublished.at" 799 | }, 800 | { 801 | "name": "Sebastian Bergmann", 802 | "email": "sebastian@phpunit.de" 803 | } 804 | ], 805 | "description": "Provides the functionality to compare PHP values for equality", 806 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 807 | "keywords": [ 808 | "comparator", 809 | "compare", 810 | "equality" 811 | ], 812 | "time": "2017-01-29T09:50:25+00:00" 813 | }, 814 | { 815 | "name": "sebastian/diff", 816 | "version": "1.4.1", 817 | "source": { 818 | "type": "git", 819 | "url": "https://github.com/sebastianbergmann/diff.git", 820 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" 821 | }, 822 | "dist": { 823 | "type": "zip", 824 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", 825 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", 826 | "shasum": "" 827 | }, 828 | "require": { 829 | "php": ">=5.3.3" 830 | }, 831 | "require-dev": { 832 | "phpunit/phpunit": "~4.8" 833 | }, 834 | "type": "library", 835 | "extra": { 836 | "branch-alias": { 837 | "dev-master": "1.4-dev" 838 | } 839 | }, 840 | "autoload": { 841 | "classmap": [ 842 | "src/" 843 | ] 844 | }, 845 | "notification-url": "https://packagist.org/downloads/", 846 | "license": [ 847 | "BSD-3-Clause" 848 | ], 849 | "authors": [ 850 | { 851 | "name": "Kore Nordmann", 852 | "email": "mail@kore-nordmann.de" 853 | }, 854 | { 855 | "name": "Sebastian Bergmann", 856 | "email": "sebastian@phpunit.de" 857 | } 858 | ], 859 | "description": "Diff implementation", 860 | "homepage": "https://github.com/sebastianbergmann/diff", 861 | "keywords": [ 862 | "diff" 863 | ], 864 | "time": "2015-12-08T07:14:41+00:00" 865 | }, 866 | { 867 | "name": "sebastian/environment", 868 | "version": "2.0.0", 869 | "source": { 870 | "type": "git", 871 | "url": "https://github.com/sebastianbergmann/environment.git", 872 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" 873 | }, 874 | "dist": { 875 | "type": "zip", 876 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 877 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 878 | "shasum": "" 879 | }, 880 | "require": { 881 | "php": "^5.6 || ^7.0" 882 | }, 883 | "require-dev": { 884 | "phpunit/phpunit": "^5.0" 885 | }, 886 | "type": "library", 887 | "extra": { 888 | "branch-alias": { 889 | "dev-master": "2.0.x-dev" 890 | } 891 | }, 892 | "autoload": { 893 | "classmap": [ 894 | "src/" 895 | ] 896 | }, 897 | "notification-url": "https://packagist.org/downloads/", 898 | "license": [ 899 | "BSD-3-Clause" 900 | ], 901 | "authors": [ 902 | { 903 | "name": "Sebastian Bergmann", 904 | "email": "sebastian@phpunit.de" 905 | } 906 | ], 907 | "description": "Provides functionality to handle HHVM/PHP environments", 908 | "homepage": "http://www.github.com/sebastianbergmann/environment", 909 | "keywords": [ 910 | "Xdebug", 911 | "environment", 912 | "hhvm" 913 | ], 914 | "time": "2016-11-26T07:53:53+00:00" 915 | }, 916 | { 917 | "name": "sebastian/exporter", 918 | "version": "2.0.0", 919 | "source": { 920 | "type": "git", 921 | "url": "https://github.com/sebastianbergmann/exporter.git", 922 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" 923 | }, 924 | "dist": { 925 | "type": "zip", 926 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 927 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 928 | "shasum": "" 929 | }, 930 | "require": { 931 | "php": ">=5.3.3", 932 | "sebastian/recursion-context": "~2.0" 933 | }, 934 | "require-dev": { 935 | "ext-mbstring": "*", 936 | "phpunit/phpunit": "~4.4" 937 | }, 938 | "type": "library", 939 | "extra": { 940 | "branch-alias": { 941 | "dev-master": "2.0.x-dev" 942 | } 943 | }, 944 | "autoload": { 945 | "classmap": [ 946 | "src/" 947 | ] 948 | }, 949 | "notification-url": "https://packagist.org/downloads/", 950 | "license": [ 951 | "BSD-3-Clause" 952 | ], 953 | "authors": [ 954 | { 955 | "name": "Jeff Welch", 956 | "email": "whatthejeff@gmail.com" 957 | }, 958 | { 959 | "name": "Volker Dusch", 960 | "email": "github@wallbash.com" 961 | }, 962 | { 963 | "name": "Bernhard Schussek", 964 | "email": "bschussek@2bepublished.at" 965 | }, 966 | { 967 | "name": "Sebastian Bergmann", 968 | "email": "sebastian@phpunit.de" 969 | }, 970 | { 971 | "name": "Adam Harvey", 972 | "email": "aharvey@php.net" 973 | } 974 | ], 975 | "description": "Provides the functionality to export PHP variables for visualization", 976 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 977 | "keywords": [ 978 | "export", 979 | "exporter" 980 | ], 981 | "time": "2016-11-19T08:54:04+00:00" 982 | }, 983 | { 984 | "name": "sebastian/global-state", 985 | "version": "1.1.1", 986 | "source": { 987 | "type": "git", 988 | "url": "https://github.com/sebastianbergmann/global-state.git", 989 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 990 | }, 991 | "dist": { 992 | "type": "zip", 993 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 994 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 995 | "shasum": "" 996 | }, 997 | "require": { 998 | "php": ">=5.3.3" 999 | }, 1000 | "require-dev": { 1001 | "phpunit/phpunit": "~4.2" 1002 | }, 1003 | "suggest": { 1004 | "ext-uopz": "*" 1005 | }, 1006 | "type": "library", 1007 | "extra": { 1008 | "branch-alias": { 1009 | "dev-master": "1.0-dev" 1010 | } 1011 | }, 1012 | "autoload": { 1013 | "classmap": [ 1014 | "src/" 1015 | ] 1016 | }, 1017 | "notification-url": "https://packagist.org/downloads/", 1018 | "license": [ 1019 | "BSD-3-Clause" 1020 | ], 1021 | "authors": [ 1022 | { 1023 | "name": "Sebastian Bergmann", 1024 | "email": "sebastian@phpunit.de" 1025 | } 1026 | ], 1027 | "description": "Snapshotting of global state", 1028 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1029 | "keywords": [ 1030 | "global state" 1031 | ], 1032 | "time": "2015-10-12T03:26:01+00:00" 1033 | }, 1034 | { 1035 | "name": "sebastian/object-enumerator", 1036 | "version": "2.0.1", 1037 | "source": { 1038 | "type": "git", 1039 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1040 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" 1041 | }, 1042 | "dist": { 1043 | "type": "zip", 1044 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", 1045 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", 1046 | "shasum": "" 1047 | }, 1048 | "require": { 1049 | "php": ">=5.6", 1050 | "sebastian/recursion-context": "~2.0" 1051 | }, 1052 | "require-dev": { 1053 | "phpunit/phpunit": "~5" 1054 | }, 1055 | "type": "library", 1056 | "extra": { 1057 | "branch-alias": { 1058 | "dev-master": "2.0.x-dev" 1059 | } 1060 | }, 1061 | "autoload": { 1062 | "classmap": [ 1063 | "src/" 1064 | ] 1065 | }, 1066 | "notification-url": "https://packagist.org/downloads/", 1067 | "license": [ 1068 | "BSD-3-Clause" 1069 | ], 1070 | "authors": [ 1071 | { 1072 | "name": "Sebastian Bergmann", 1073 | "email": "sebastian@phpunit.de" 1074 | } 1075 | ], 1076 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1077 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1078 | "time": "2017-02-18T15:18:39+00:00" 1079 | }, 1080 | { 1081 | "name": "sebastian/recursion-context", 1082 | "version": "2.0.0", 1083 | "source": { 1084 | "type": "git", 1085 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1086 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" 1087 | }, 1088 | "dist": { 1089 | "type": "zip", 1090 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1091 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1092 | "shasum": "" 1093 | }, 1094 | "require": { 1095 | "php": ">=5.3.3" 1096 | }, 1097 | "require-dev": { 1098 | "phpunit/phpunit": "~4.4" 1099 | }, 1100 | "type": "library", 1101 | "extra": { 1102 | "branch-alias": { 1103 | "dev-master": "2.0.x-dev" 1104 | } 1105 | }, 1106 | "autoload": { 1107 | "classmap": [ 1108 | "src/" 1109 | ] 1110 | }, 1111 | "notification-url": "https://packagist.org/downloads/", 1112 | "license": [ 1113 | "BSD-3-Clause" 1114 | ], 1115 | "authors": [ 1116 | { 1117 | "name": "Jeff Welch", 1118 | "email": "whatthejeff@gmail.com" 1119 | }, 1120 | { 1121 | "name": "Sebastian Bergmann", 1122 | "email": "sebastian@phpunit.de" 1123 | }, 1124 | { 1125 | "name": "Adam Harvey", 1126 | "email": "aharvey@php.net" 1127 | } 1128 | ], 1129 | "description": "Provides functionality to recursively process PHP variables", 1130 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1131 | "time": "2016-11-19T07:33:16+00:00" 1132 | }, 1133 | { 1134 | "name": "sebastian/resource-operations", 1135 | "version": "1.0.0", 1136 | "source": { 1137 | "type": "git", 1138 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1139 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 1140 | }, 1141 | "dist": { 1142 | "type": "zip", 1143 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1144 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1145 | "shasum": "" 1146 | }, 1147 | "require": { 1148 | "php": ">=5.6.0" 1149 | }, 1150 | "type": "library", 1151 | "extra": { 1152 | "branch-alias": { 1153 | "dev-master": "1.0.x-dev" 1154 | } 1155 | }, 1156 | "autoload": { 1157 | "classmap": [ 1158 | "src/" 1159 | ] 1160 | }, 1161 | "notification-url": "https://packagist.org/downloads/", 1162 | "license": [ 1163 | "BSD-3-Clause" 1164 | ], 1165 | "authors": [ 1166 | { 1167 | "name": "Sebastian Bergmann", 1168 | "email": "sebastian@phpunit.de" 1169 | } 1170 | ], 1171 | "description": "Provides a list of PHP built-in functions that operate on resources", 1172 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1173 | "time": "2015-07-28T20:34:47+00:00" 1174 | }, 1175 | { 1176 | "name": "sebastian/version", 1177 | "version": "2.0.1", 1178 | "source": { 1179 | "type": "git", 1180 | "url": "https://github.com/sebastianbergmann/version.git", 1181 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1182 | }, 1183 | "dist": { 1184 | "type": "zip", 1185 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1186 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1187 | "shasum": "" 1188 | }, 1189 | "require": { 1190 | "php": ">=5.6" 1191 | }, 1192 | "type": "library", 1193 | "extra": { 1194 | "branch-alias": { 1195 | "dev-master": "2.0.x-dev" 1196 | } 1197 | }, 1198 | "autoload": { 1199 | "classmap": [ 1200 | "src/" 1201 | ] 1202 | }, 1203 | "notification-url": "https://packagist.org/downloads/", 1204 | "license": [ 1205 | "BSD-3-Clause" 1206 | ], 1207 | "authors": [ 1208 | { 1209 | "name": "Sebastian Bergmann", 1210 | "email": "sebastian@phpunit.de", 1211 | "role": "lead" 1212 | } 1213 | ], 1214 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1215 | "homepage": "https://github.com/sebastianbergmann/version", 1216 | "time": "2016-10-03T07:35:21+00:00" 1217 | }, 1218 | { 1219 | "name": "symfony/yaml", 1220 | "version": "v3.2.6", 1221 | "source": { 1222 | "type": "git", 1223 | "url": "https://github.com/symfony/yaml.git", 1224 | "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a" 1225 | }, 1226 | "dist": { 1227 | "type": "zip", 1228 | "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a", 1229 | "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a", 1230 | "shasum": "" 1231 | }, 1232 | "require": { 1233 | "php": ">=5.5.9" 1234 | }, 1235 | "require-dev": { 1236 | "symfony/console": "~2.8|~3.0" 1237 | }, 1238 | "suggest": { 1239 | "symfony/console": "For validating YAML files using the lint command" 1240 | }, 1241 | "type": "library", 1242 | "extra": { 1243 | "branch-alias": { 1244 | "dev-master": "3.2-dev" 1245 | } 1246 | }, 1247 | "autoload": { 1248 | "psr-4": { 1249 | "Symfony\\Component\\Yaml\\": "" 1250 | }, 1251 | "exclude-from-classmap": [ 1252 | "/Tests/" 1253 | ] 1254 | }, 1255 | "notification-url": "https://packagist.org/downloads/", 1256 | "license": [ 1257 | "MIT" 1258 | ], 1259 | "authors": [ 1260 | { 1261 | "name": "Fabien Potencier", 1262 | "email": "fabien@symfony.com" 1263 | }, 1264 | { 1265 | "name": "Symfony Community", 1266 | "homepage": "https://symfony.com/contributors" 1267 | } 1268 | ], 1269 | "description": "Symfony Yaml Component", 1270 | "homepage": "https://symfony.com", 1271 | "time": "2017-03-07T16:47:02+00:00" 1272 | }, 1273 | { 1274 | "name": "webmozart/assert", 1275 | "version": "1.2.0", 1276 | "source": { 1277 | "type": "git", 1278 | "url": "https://github.com/webmozart/assert.git", 1279 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" 1280 | }, 1281 | "dist": { 1282 | "type": "zip", 1283 | "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", 1284 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", 1285 | "shasum": "" 1286 | }, 1287 | "require": { 1288 | "php": "^5.3.3 || ^7.0" 1289 | }, 1290 | "require-dev": { 1291 | "phpunit/phpunit": "^4.6", 1292 | "sebastian/version": "^1.0.1" 1293 | }, 1294 | "type": "library", 1295 | "extra": { 1296 | "branch-alias": { 1297 | "dev-master": "1.3-dev" 1298 | } 1299 | }, 1300 | "autoload": { 1301 | "psr-4": { 1302 | "Webmozart\\Assert\\": "src/" 1303 | } 1304 | }, 1305 | "notification-url": "https://packagist.org/downloads/", 1306 | "license": [ 1307 | "MIT" 1308 | ], 1309 | "authors": [ 1310 | { 1311 | "name": "Bernhard Schussek", 1312 | "email": "bschussek@gmail.com" 1313 | } 1314 | ], 1315 | "description": "Assertions to validate method input/output with nice error messages.", 1316 | "keywords": [ 1317 | "assert", 1318 | "check", 1319 | "validate" 1320 | ], 1321 | "time": "2016-11-23T20:04:58+00:00" 1322 | } 1323 | ], 1324 | "aliases": [], 1325 | "minimum-stability": "stable", 1326 | "stability-flags": [], 1327 | "prefer-stable": false, 1328 | "prefer-lowest": false, 1329 | "platform": { 1330 | "php": ">=5.5" 1331 | }, 1332 | "platform-dev": [] 1333 | } 1334 | -------------------------------------------------------------------------------- /implementations/php/composer.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fulldecent/structured-acceptance-test/e9710e46668163eb126692fe527a274190f979ee/implementations/php/composer.phar -------------------------------------------------------------------------------- /implementations/php/sample-producer.php: -------------------------------------------------------------------------------- 1 | failure = true; 17 | $finding->rule = 'Something is wrong'; 18 | $finding->description = 'You did something wrong'; 19 | $location = new \StructuredAcceptanceTest\StatFindingLocation; 20 | $location->path = 'aFile.txt'; 21 | $location->beginLine = 5; 22 | $finding->location = $location; 23 | yield $finding; 24 | } 25 | } 26 | 27 | // Main program 28 | $process = new \StructuredAcceptanceTest\StatProcess; 29 | $process->name = 'Example test'; 30 | $process->version = '0.0.1'; 31 | $statOutput = new \StructuredAcceptanceTest\StatOutput; 32 | $statOutput->process = $process; 33 | $statOutput->findings = generateFindings(); 34 | 35 | #$printer = new \StructuredAcceptanceTest\StatPrinterJson(); 36 | $printer = new \StructuredAcceptanceTest\StatPrinterPretty(); 37 | $printer->output($statOutput); 38 | -------------------------------------------------------------------------------- /implementations/php/src/StructuredAcceptanceTest/StatFinding.php: -------------------------------------------------------------------------------- 1 | statVersion\",\n"; 11 | echo " \"process\": {\n"; 12 | foreach (array_filter(get_object_vars($statOutput->process)) as $var => $val) { 13 | $jsonVar = json_encode($var); 14 | $jsonVal = json_encode($val); 15 | echo " $jsonVar: $jsonVal\n"; 16 | } 17 | echo " }\n"; 18 | 19 | //RIGHT NOW THIS ASSUMES WE HAVE A GENERATOR 20 | $statOutput->findings->rewind(); 21 | if ($statOutput->findings->valid()) { 22 | echo " \"findings:\" [\n"; 23 | $isFirstFinding = true; 24 | foreach ($statOutput->findings as $finding) { 25 | echo $isFirstFinding ? ' ' : ' ,'; 26 | echo json_encode($finding) . "\n"; 27 | $isFirstFinding = false; 28 | } 29 | echo " ]\n"; 30 | } 31 | 32 | // print findings 33 | 34 | echo "}\n"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /implementations/php/src/StructuredAcceptanceTest/StatPrinterPretty.php: -------------------------------------------------------------------------------- 1 | process->name; 11 | if (!empty($statOutput->process->version)) { 12 | echo ' ' . $statOutput->process->version; 13 | } 14 | if (!empty($statOutput->process->website)) { 15 | echo ', ' . $statOutput->process->website; 16 | } 17 | echo PHP_EOL; 18 | 19 | // SEE ALL SITUATIONS at https://github.com/fulldecent/structured-acceptance-test/issues/26 20 | 21 | $totalWarnings = 0; 22 | $totalErrors = 0; 23 | 24 | // TODO ADD COLOR 25 | foreach ($statOutput->findings as $finding) { 26 | echo $finding->failure ? '🛑 ' : '⚠️ '; 27 | if (!empty($finding->location) && !empty($finding->location->path)) { 28 | echo $finding->location->path; 29 | if (!empty($finding->location->beginLine)) { 30 | echo ' line ' . $finding->location->beginLine; 31 | if (!empty($finding->location->beginColumn)) { 32 | echo ' col ' . $finding->location->beginColumn; 33 | } 34 | if (!empty($finding->location->endLine) && $finding->location->begin != $finding->location->endLine) { 35 | echo ' to line ' . $finding->location->endLine; 36 | if (!empty($finding->location->endColumn)) { 37 | echo ' col ' . $finding->location->endColumn; 38 | } 39 | } 40 | } 41 | echo "\n "; 42 | } 43 | echo $finding->description . PHP_EOL; 44 | } 45 | 46 | //TODO ADD SUMMARY 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /implementations/php/src/StructuredAcceptanceTest/StatProcess.php: -------------------------------------------------------------------------------- 1 | 0.8, >= 0.8.1) 6 | json-schema (~> 3.0) 7 | safe-enum (~> 0.3, >= 0.3.0) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | addressable (2.8.1) 13 | public_suffix (>= 2.0.2, < 6.0) 14 | colorize (0.8.1) 15 | json-schema (3.0.0) 16 | addressable (>= 2.8) 17 | public_suffix (5.0.1) 18 | safe-enum (0.3.1) 19 | 20 | PLATFORMS 21 | arm64-darwin-21 22 | 23 | DEPENDENCIES 24 | structured-acceptance-test! 25 | 26 | BUNDLED WITH 27 | 2.4.1 28 | -------------------------------------------------------------------------------- /implementations/ruby/README.md: -------------------------------------------------------------------------------- 1 | # Ruby Gem 2 | 3 | This data structure allows you to store STAT information in memory. 4 | 5 | This Gem also provides incremental printing so that you can output each finding as it is made. 6 | 7 | ## Usage 8 | 9 | In your Ruby project, add this to your Gemfile: 10 | 11 | ``` 12 | source 'http://rubygems.org' 13 | 14 | gem 'structured-acceptance-test' 15 | ``` 16 | 17 | See an example program that outputs findings at https://github.com/fulldecent/structured-acceptance-test/blob/master/tools/example-process/example-process.rb 18 | 19 | ## Atom linter 20 | 21 | Your process will produce output that can also be consumed by Atom. See our example atom process in the `tools/` folder. 22 | 23 | ![stat](https://cloud.githubusercontent.com/assets/11000048/23030871/20e28fba-f480-11e6-96d6-23c5e2d5afd2.gif) 24 | 25 | ## Contributing 26 | 27 | Publish a new version to rubygems.org: 28 | 29 | ```sh 30 | rm structured-acceptance-test-*.gem 31 | gem build structured-acceptance-test.gemspec 32 | gem push structured-acceptance-test-*.gem 33 | ``` 34 | -------------------------------------------------------------------------------- /implementations/ruby/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |task| 4 | task.libs << %w(test lib) 5 | task.pattern = 'test/test_*.rb' 6 | end 7 | 8 | task :default => :test -------------------------------------------------------------------------------- /implementations/ruby/lib/JSONable.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require 'json' 3 | require 'colorize' 4 | 5 | class JSONable 6 | 7 | FORMATTING_STAR = '⭐' 8 | FORMATTING_CHECKMARK = '✅' 9 | FORMATTING_BALL = '⚫' 10 | FORMATTING_WARNING = '⚠' 11 | 12 | ## 13 | # Initialize object extending JSONable 14 | # 15 | # Params: 16 | # +hash+:: Hash 17 | def initialize(hash) 18 | if hash.is_a? Hash 19 | hash.each do |k, v| 20 | if v.is_a? Array 21 | items = [] 22 | v.each { |i| 23 | case k 24 | when 'findings' 25 | item = StatModule::Finding.new(nil, nil, nil, i) 26 | when 'fixes' 27 | item = StatModule::Fix.new(nil, i) 28 | when 'traces' 29 | item = StatModule::Location.new(nil, i) 30 | else 31 | v = item 32 | end 33 | items.push(item) 34 | } 35 | v = items 36 | end 37 | if v.is_a? Hash 38 | case k 39 | when 'process' 40 | v = StatModule::Process.new(nil, v) 41 | when 'location' 42 | v = StatModule::Location.new(nil, v) 43 | when 'detail' 44 | v = StatModule::Detail.new(nil, v) 45 | end 46 | end 47 | self.instance_variable_set("@#{k}", v) ## create and initialize an instance variable for this key/value pair 48 | self.class.send(:define_method, k, proc { self.instance_variable_get("@#{k}") }) ## create the getter that returns the instance variable 49 | self.class.send(:define_method, "#{k}=", proc { |v| self.instance_variable_set("@#{k}", v) }) ## create the setter that sets the instance variable 50 | end 51 | end 52 | end 53 | 54 | ## 55 | # Get object in pretty json format 56 | # 57 | # Params: 58 | # +excluded_fields+:: array of String - attributes to exclude 59 | def to_json(excluded_fields = []) 60 | hash = {} 61 | self.instance_variables.each do |var| 62 | hash[var.to_s.delete "@"] = self.instance_variable_get var unless excluded_fields.is_a?(Array) && excluded_fields.include?(var.to_s.delete "@") 63 | end 64 | JSON.pretty_generate(hash) 65 | end 66 | 67 | ## 68 | # Generate Hash from json string 69 | def self.from_json!(string) 70 | JSON.load(string).each do |var, val| 71 | self.instance_variable_set '@' + var, val 72 | end 73 | end 74 | end 75 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/category.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require 'enum' 3 | 4 | ## 5 | # Category can be: 6 | # * Bug Risk — the meaning is likely to not be what the author intended 7 | # * Clarity — the meaning is unclear 8 | # * Complexity — the meaning should be broken into smaller pieces 9 | # * Duplication — unnecessary duplication was found 10 | # * Performance — an inefficient approach was used 11 | # * Security — a situation may allow access to something that should be allowed 12 | # * Style — the style could be improved 13 | class Category < Enum::Base 14 | values 'Bug Risk', 'Clarity', 'Associative', 'Complexity', 'Duplication', 'Performance', 'Security', 'Style' 15 | end 16 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/detail.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require_relative 'JSONable' 3 | 4 | class Detail < JSONable 5 | 6 | ## 7 | # Initialize new Detail object 8 | # 9 | # Params: 10 | # +body+:: String, required 11 | # +hash+:: Hash, can be null 12 | def initialize(body, hash = nil) 13 | if hash.is_a? Hash 14 | super(hash) 15 | return 16 | end 17 | 18 | raise TypeException unless body.is_a?(String) 19 | @body = body 20 | end 21 | 22 | ## 23 | # Set body 24 | # 25 | # Params: 26 | # +body+:: String 27 | def body=(body) 28 | raise TypeException unless body.is_a?(String) 29 | @body = body 30 | end 31 | 32 | ## 33 | # Get body 34 | def body 35 | @body 36 | end 37 | 38 | ## 39 | # Set trace 40 | # 41 | # Params: 42 | # +trace+:: array of StatModule::Location objects 43 | def trace=(trace) 44 | raise TypeException unless trace.is_a?(Array) 45 | trace.each { |item| 46 | raise TypeException unless item.is_a?(StatModule::Location) 47 | raise DuplicateElementException if @trace.include?(item) 48 | @trace.push(item) 49 | } 50 | end 51 | 52 | ## 53 | # Get trace 54 | def trace 55 | @trace 56 | end 57 | end 58 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/duplicate_element_exception.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | ## 3 | # Duplicated element in array exception 4 | class DuplicateElementException < Exception 5 | end 6 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/finding.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require_relative 'JSONable' 3 | 4 | class Finding < JSONable 5 | 6 | ## 7 | # Initialize new Finding object 8 | # 9 | # Params: 10 | # +failure+:: boolean, required 11 | # +rule+:: String, required 12 | # +description+:: String, required 13 | # +hash+:: Hash, can be null 14 | def initialize(failure, rule, description, hash = nil) 15 | if hash.is_a? Hash 16 | super(hash) 17 | return 18 | end 19 | 20 | raise TypeException unless rule.is_a?(String) && description.is_a?(String) 21 | @failure = failure 22 | @rule = rule 23 | @description = description 24 | end 25 | 26 | ## 27 | # Set failure 28 | # 29 | # Params: 30 | # +failure+:: boolean 31 | def failure=(failure) 32 | @failure = failure 33 | end 34 | 35 | ## 36 | # Get failure 37 | def failure 38 | @failure 39 | end 40 | 41 | ## 42 | # Set rule 43 | # 44 | # Params: 45 | # +rule+:: String 46 | def rule=(rule) 47 | raise TypeException unless rule.is_a?(String) 48 | @rule = rule 49 | end 50 | 51 | ## 52 | # Get rule 53 | def rule 54 | @rule 55 | end 56 | 57 | ## 58 | # Set description 59 | # 60 | # Params: 61 | # +description+:: String 62 | def description=(description) 63 | raise TypeException unless description.is_a?(String) 64 | @description = description 65 | end 66 | 67 | ## 68 | # Get description 69 | def description 70 | @description 71 | end 72 | 73 | ## 74 | # Set detail 75 | # 76 | # Params: 77 | # +detail+:: StatModule::Detail 78 | def detail=(detail) 79 | raise TypeException unless detail.is_a?(StatModule::Detail) 80 | @detail = detail 81 | end 82 | 83 | def detail 84 | @detail 85 | end 86 | 87 | ## 88 | # Set array of categories 89 | # 90 | # Params: 91 | # +categories+:: array of StatModule::Category 92 | def categories=(categories) 93 | raise TypeException unless categories.is_a?(Array) 94 | categories.each { |item| 95 | raise TypeException unless Category.all.include?(item) 96 | raise DuplicateElementException if @categories.include?(item) 97 | @categories.push(item) 98 | } 99 | end 100 | 101 | ## 102 | # Get array of StatModule::Category objects 103 | def categories 104 | @categories 105 | end 106 | 107 | ## 108 | # Set location 109 | # 110 | # Params: 111 | # +location+:: StatModule::Location 112 | def location=(location) 113 | raise TypeException unless location.is_a?(Location) 114 | @location = location 115 | end 116 | 117 | ## 118 | # Get location 119 | def location 120 | @location 121 | end 122 | 123 | ## 124 | # Set time to fix 125 | # 126 | # Params: 127 | # +time_to_fix+:: Integer 128 | def time_to_fix=(time_to_fix) 129 | raise TypeException unless time_to_fix.is_a?(Integer) 130 | @timeToFix = time_to_fix 131 | end 132 | 133 | ## 134 | # Get time to fix 135 | def time_to_fix 136 | @timeToFix 137 | end 138 | 139 | ## 140 | # Set recommendation 141 | # 142 | # Params: 143 | # +recommendation+:: String 144 | def recommendation=(recommendation) 145 | raise TypeException unless recommendation.is_a?(String) 146 | @recommendation = recommendation 147 | end 148 | 149 | ## 150 | # Get recommendation 151 | def recommendation 152 | @recommendation 153 | end 154 | 155 | ## 156 | # Set fixes 157 | # 158 | # Params: 159 | # +fixes+:: array of StatModule::Fix 160 | def fixes=(fixes) 161 | raise TypeException unless fixes.is_a?(Array) 162 | fixes.each { |item| 163 | raise TypeException unless item.is_a?(StatModule::Fix) 164 | raise DuplicateElementException if @fixes.include?(item) 165 | @fixes.push(item) 166 | } 167 | end 168 | 169 | ## 170 | # Get array of StatModule::Fix objects 171 | def fixes 172 | @fixes 173 | end 174 | 175 | ## 176 | # Get formatted information about findings 177 | # 178 | # Params: 179 | # 180 | # +formatted+:: indicate weather print boring or pretty colorful finding 181 | def print(formatted = false) 182 | result = "#{rule}, #{description}" 183 | if formatted 184 | if failure 185 | result = "#{FORMATTING_BALL} #{result}".colorize(:red) 186 | else 187 | result = "#{FORMATTING_WARNING} #{result}".colorize(:yellow) 188 | end 189 | end 190 | result += "\n#{location.print}" unless location.nil? 191 | result += "\nRECOMMENDATION: #{recommendation}" unless recommendation.nil? 192 | result 193 | end 194 | end 195 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/fix.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require_relative 'JSONable' 3 | 4 | class Fix < JSONable 5 | 6 | ## 7 | # Initialize new Fix object 8 | # 9 | # Params: 10 | # +location+:: StatModule::Location, required 11 | # +hash+:: Hash, can be null 12 | def initialize(location, hash = nil) 13 | if hash.is_a? Hash 14 | super(hash) 15 | return 16 | end 17 | @location = location 18 | end 19 | 20 | ## 21 | # Set location 22 | # 23 | # Params: 24 | # +location+:: StatModule::Location 25 | def location=(location) 26 | raise TypeException unless location.is_a?(StatModule::Location) 27 | @location = location 28 | end 29 | 30 | ## 31 | # Get location 32 | def location 33 | @location 34 | end 35 | 36 | ## 37 | # Set new text 38 | # 39 | # Params: 40 | # +new_text+:: String 41 | def new_text=(new_text) 42 | raise TypeException unless new_text.is_a?(String) 43 | @newText = new_text 44 | end 45 | 46 | ## 47 | # Get new text 48 | def new_text 49 | @newText 50 | end 51 | end 52 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/index_out_of_bound_exception.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | ## 3 | # Index out of array bound exception 4 | class IndexOutOfBoundException < Exception 5 | end 6 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/location.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require_relative 'JSONable' 3 | 4 | class Location < JSONable 5 | 6 | ## 7 | # Initialize new Location object 8 | # 9 | # Params: 10 | # +process+:: String, required 11 | # +hash+:: Hash, can be null 12 | def initialize(path, hash = nil) 13 | if hash.is_a? Hash 14 | super(hash) 15 | return 16 | end 17 | raise TypeException unless path.is_a?(String) 18 | @path = path 19 | end 20 | 21 | ## 22 | # Set path 23 | # 24 | # Params: 25 | # +path+:: String 26 | def path=(path) 27 | raise TypeException unless path.is_a?(String) 28 | @path = path 29 | end 30 | 31 | ## 32 | # Get path 33 | def path 34 | @path 35 | end 36 | 37 | ## 38 | # Set begin line 39 | # 40 | # Params: 41 | # +begin_line+:: Integer 42 | def begin_line=(begin_line) 43 | raise TypeException unless begin_line.is_a?(Integer) 44 | @beginLine = begin_line 45 | end 46 | 47 | ## 48 | # Get begin line number 49 | def begin_line 50 | @beginLine 51 | end 52 | 53 | ## 54 | # Set begin column 55 | # 56 | # Params: 57 | # +begin_column+:: Integer 58 | def begin_column=(begin_column) 59 | raise TypeException unless begin_column.is_a?(Integer) 60 | @beginColumn = begin_column 61 | end 62 | 63 | ## 64 | # Get begin column number 65 | def begin_column 66 | @beginColumn 67 | end 68 | 69 | ## 70 | # Set end line 71 | # 72 | # Params: 73 | # +end_line+:: Integer 74 | def end_line=(end_line) 75 | raise TypeException unless end_line.is_a?(Integer) 76 | @endLine = end_line 77 | end 78 | 79 | ## 80 | # Get end line number 81 | def end_line 82 | @endLine 83 | end 84 | 85 | ## 86 | # Set end column 87 | # 88 | # Params: 89 | # +end_column+:: Integer 90 | def end_column=(end_column) 91 | raise TypeException unless end_column.is_a?(Integer) 92 | @endColumn = end_column 93 | end 94 | 95 | ## 96 | # Get end column number 97 | def end_column 98 | @endColumn 99 | end 100 | 101 | ## 102 | # Get formatted information about location 103 | def print 104 | result = "in #{path}" 105 | if !begin_line.nil? && !end_line.nil? 106 | if begin_line != end_line 107 | if !begin_column.nil? && !end_column.nil? 108 | result += ", line #{begin_line}:#{begin_column} to line #{end_line}:#{end_column}" 109 | elsif !begin_column.nil? && end_column.nil? 110 | result += ", line #{begin_line}:#{begin_column} to line #{end_line}" 111 | elsif begin_column.nil? && !end_column.nil? 112 | result += ", line #{begin_line} to line #{end_line}:#{end_column}" 113 | else 114 | result += ", lines #{begin_line}-#{end_line}" 115 | end 116 | else 117 | if begin_column.nil? 118 | result += ", line #{begin_line}" 119 | else 120 | result += ", line #{begin_line}:#{begin_column}" 121 | result += "-#{end_column}" unless end_column.nil? 122 | end 123 | end 124 | end 125 | result 126 | end 127 | end 128 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/process.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | 3 | require_relative 'JSONable' 4 | 5 | class Process < JSONable 6 | 7 | ## 8 | # Initialize new Process object 9 | # 10 | # Params: 11 | # +name+:: String, name of the process, required 12 | # +hash+:: Hash, can be null 13 | def initialize(name, hash = nil) 14 | if hash.is_a? Hash 15 | super(hash) 16 | return 17 | end 18 | 19 | raise TypeException unless name.is_a?(String) 20 | @name = name 21 | end 22 | 23 | ## 24 | # Set name of the process 25 | # 26 | # Params: 27 | # +name+:: String, name of the process, required 28 | def name=(name) 29 | raise TypeException unless name.is_a?(String) 30 | @name = name 31 | end 32 | 33 | ## 34 | # Get name of the process 35 | def name 36 | @name 37 | end 38 | 39 | ## 40 | # Set version 41 | # 42 | # Params: 43 | # +version+:: String 44 | def version=(version) 45 | raise TypeException unless version.is_a?(String) 46 | @version = version 47 | end 48 | 49 | ## 50 | # Get version of the process 51 | def version 52 | @version 53 | end 54 | 55 | ## 56 | # Set description 57 | # 58 | # Params: 59 | # +description+:: String 60 | def description=(description) 61 | raise TypeException unless description.is_a?(String) 62 | @description = description 63 | end 64 | 65 | ## 66 | # Get description 67 | def description 68 | @description 69 | end 70 | 71 | ## 72 | # Set maintainer 73 | # 74 | # Params: 75 | # +maintainer+:: String 76 | def maintainer=(maintainer) 77 | raise TypeException unless maintainer.is_a?(String) 78 | @maintainer = maintainer 79 | end 80 | 81 | ## 82 | # Get maintainer 83 | def maintainer 84 | @maintainer 85 | end 86 | 87 | ## 88 | # Set email 89 | # 90 | # Params: 91 | # +email+:: String 92 | def email=(email) 93 | raise TypeException unless email.is_a?(String) 94 | @email = email 95 | end 96 | 97 | ## 98 | # Get email 99 | def email 100 | @email 101 | end 102 | 103 | ## 104 | # Set website 105 | # 106 | # Params: 107 | # +website+:: String 108 | def website=(website) 109 | raise TypeException unless website.is_a?(String) 110 | @website = website 111 | end 112 | 113 | ## 114 | # Get website 115 | def website 116 | @website 117 | end 118 | 119 | ## 120 | # Set repeatability 121 | # 122 | # Params: 123 | # +repeatability+:: String 124 | def repeatability=(repeatability) 125 | raise TypeException unless Repeatability.all.include?(repeatability) 126 | @repeatability = repeatability 127 | end 128 | 129 | ## 130 | # Get repeatability 131 | def repeatability 132 | @repeatability 133 | end 134 | 135 | ## 136 | # Get formatted information about process 137 | # 138 | # Params: 139 | # 140 | # +formatted+:: indicate weather print boring or pretty colorful process 141 | def print(formatted = nil) 142 | result = name 143 | unless version.nil? 144 | result += ", version #{version}" 145 | end 146 | if formatted 147 | result = "#{FORMATTING_STAR.colorize(:yellow)} #{result}" 148 | end 149 | result 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /implementations/ruby/lib/repeatability.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | require 'enum' 3 | 4 | ## 5 | # Repeatability can be: 6 | # * Volatile — findings MAY change when repeating validation on the identical targets 7 | # * Repeatable — Findings MUST be identical if the program is run again with the same inputs 8 | # * Associative — Findings for targets [a, b] MUST equal the union of findings for [a] and [b] -- in other words, if only one file is changed, only that file need be tested 9 | class Repeatability < Enum::Base 10 | values 'Volatile', 'Repeatable', 'Associative' 11 | end 12 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/structured-acceptance-test.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | 3 | require_relative 'detail' 4 | require_relative 'finding' 5 | require_relative 'fix' 6 | require_relative 'location' 7 | require_relative 'process' 8 | require_relative 'repeatability' 9 | require_relative 'category' 10 | require_relative 'JSONable' 11 | require_relative 'type_exception' 12 | require_relative 'duplicate_element_exception' 13 | require_relative 'index_out_of_bound_exception' 14 | 15 | class Stat < JSONable 16 | attr_reader :statVersion 17 | 18 | ## 19 | # Initialize new Stat object 20 | # 21 | # Params: 22 | # +process+:: StatModule::Process, required 23 | # +hash+:: Hash, can be null 24 | def initialize(process, hash = nil) 25 | @finding_print_index = 0 26 | @findings = [] 27 | 28 | if hash.is_a? Hash 29 | super(hash) 30 | return 31 | end 32 | 33 | raise TypeException unless process.is_a?(StatModule::Process) 34 | @statVersion = '1.0.0' 35 | @process = process 36 | end 37 | 38 | ## 39 | # Set array of findings 40 | # 41 | # Params: 42 | # +findings+:: Array of StatModule::Finding 43 | def findings=(findings) 44 | raise TypeException unless findings.is_a?(Array) 45 | findings.each { |item| 46 | raise TypeException unless item.is_a?(StatModule::Finding) 47 | raise DuplicateElementException if @findings.include?(item) 48 | @findings.push(item) 49 | } 50 | end 51 | 52 | ## 53 | # Get array of findings 54 | def findings 55 | @findings 56 | end 57 | 58 | ## 59 | # Set process 60 | # 61 | # Params: 62 | # +process+:: instance of StatModule::Process 63 | def process=(process) 64 | raise TypeException unless process.is_a?(StatModule::Process) 65 | @process = process 66 | end 67 | 68 | ## 69 | # Get process 70 | def process 71 | @process 72 | end 73 | 74 | ## 75 | # Prints header of STAT object in json format 76 | # Header contains statVersion, process and optional array of findings 77 | def print_header 78 | @finding_print_index = 0 79 | hash = {} 80 | hash['statVersion'] = @statVersion 81 | hash['process'] = @process 82 | hash['findings'] = [] 83 | result = hash.to_json 84 | result = result[0..result.length - 3] 85 | puts(result) 86 | puts 87 | $stdout.flush 88 | end 89 | 90 | ## 91 | # Prints one finding in json format. 92 | def print_finding 93 | if @finding_print_index < @findings.length 94 | result = @findings[@finding_print_index].to_json 95 | result += ',' unless @finding_print_index >= @findings.length - 1 96 | puts result 97 | puts 98 | $stdout.flush 99 | @finding_print_index += 1 100 | else 101 | raise IndexOutOfBoundException 102 | end 103 | end 104 | 105 | ## 106 | # Prints footer of STAT object in json format 107 | def print_footer 108 | @finding_print_index = 0 109 | puts ']}' 110 | puts 111 | $stdout.flush 112 | end 113 | 114 | ## 115 | # Get STAT object in json format 116 | def to_json(options = {}) 117 | super(['finding_print_index']) 118 | end 119 | 120 | ## 121 | # Get statistic information about findings 122 | # 123 | # Params: 124 | # 125 | # +formatted+:: indicate weather print boring or pretty colorful statistic 126 | def summary_print(formatted = false) 127 | errors = 0 128 | warnings = 0 129 | findings.each { |finding| 130 | if finding.failure 131 | errors += 1 132 | else 133 | warnings += 1 134 | end 135 | } 136 | if errors == 0 && warnings == 0 137 | result = "#{FORMATTING_CHECKMARK} PASSED with no warning".colorize(:green) 138 | elsif errors == 0 139 | result = "#{FORMATTING_WARNING} PASSED with #{warnings} warning".colorize(:yellow) 140 | elsif warnings == 0 141 | result = "#{FORMATTING_BALL} FAILED with #{errors} error".colorize(:red) 142 | else 143 | result = "#{FORMATTING_BALL} FAILED with #{errors} error and #{warnings} warning".colorize(:red) 144 | end 145 | if formatted 146 | result 147 | else 148 | result[result.index(' ') + 1..result.length] 149 | end 150 | end 151 | end 152 | end -------------------------------------------------------------------------------- /implementations/ruby/lib/type_exception.rb: -------------------------------------------------------------------------------- 1 | module StatModule 2 | ## 3 | # Wrong type exception 4 | class TypeException < Exception 5 | end 6 | end -------------------------------------------------------------------------------- /implementations/ruby/structured-acceptance-test.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'structured-acceptance-test' 3 | s.version = '0.0.8' 4 | s.summary = 'Structured acceptance test' 5 | s.description = 'Structured acceptance test data structure gem' 6 | s.authors = ['William Entriken', 'Ilia Grabko'] 7 | s.email = 'github.com@phor.net' 8 | s.files = [ 9 | 'lib/structured-acceptance-test.rb', 10 | 'lib/category.rb', 11 | 'lib/detail.rb', 12 | 'lib/duplicate_element_exception.rb', 13 | 'lib/finding.rb', 14 | 'lib/fix.rb', 15 | 'lib/index_out_of_bound_exception.rb', 16 | 'lib/JSONable.rb', 17 | 'lib/location.rb', 18 | 'lib/process.rb', 19 | 'lib/repeatability.rb', 20 | 'lib/type_exception.rb', 21 | ] 22 | s.homepage = 'https://github.com/fulldecent/structured-acceptance-test' 23 | s.license = 'MIT' 24 | 25 | s.add_runtime_dependency 'colorize', '~> 0.8', '>= 0.8.1' 26 | end 27 | -------------------------------------------------------------------------------- /implementations/ruby/test/test_structured-acceptance-test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative '../lib/structured-acceptance-test' 3 | 4 | class TestStat < Minitest::Test 5 | def setup 6 | @process = StatModule::Process.new('process name') 7 | @stat = StatModule::Stat.new(@process) 8 | end 9 | 10 | def test_findings_empty 11 | assert_equal 0, @stat.findings.length 12 | end 13 | 14 | def test_findings_not_empty 15 | @finding = StatModule::Finding.new(true, 'some rule', 'some description') 16 | @stat.findings = [@finding] 17 | assert_equal 1, @stat.findings.length 18 | end 19 | 20 | def test_finding_assertion 21 | assert_raises StatModule::TypeException do 22 | @stat.findings = "String" 23 | end 24 | end 25 | 26 | def test_stat_print_out_of_bound 27 | @finding1 = StatModule::Finding.new(true, 'some rule', 'some description') 28 | @finding2 = StatModule::Finding.new(true, 'some rule2', 'some description2') 29 | @stat.findings =[@finding1, @finding2] 30 | assert_raises StatModule::IndexOutOfBoundException do 31 | @stat.print_header 32 | @stat.print_finding 33 | @stat.print_finding 34 | @stat.print_finding 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /statJsonSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "required": [ 5 | "statVersion", 6 | "process" 7 | ], 8 | "properties": { 9 | "statVersion": { 10 | "type": "string" 11 | }, 12 | "process": { 13 | "$ref": "#/definitions/process" 14 | }, 15 | "findings": { 16 | "type": "array", 17 | "items": { 18 | "$ref": "#/definitions/finding" 19 | }, 20 | "uniqueItems": true 21 | } 22 | }, 23 | "definitions": { 24 | "process": { 25 | "type": "object", 26 | "properties": { 27 | "name": { 28 | "type": "string" 29 | }, 30 | "version": { 31 | "type": "string" 32 | }, 33 | "description": { 34 | "type": "string" 35 | }, 36 | "maintainer": { 37 | "type": "string" 38 | }, 39 | "email": { 40 | "type": "string" 41 | }, 42 | "website": { 43 | "type": "string" 44 | }, 45 | "repeatability": { 46 | "type": { 47 | "enum": [ 48 | "Volatile", 49 | "Repeatable", 50 | "Associative" 51 | ] 52 | } 53 | } 54 | }, 55 | "required": [ 56 | "name" 57 | ] 58 | }, 59 | "finding": { 60 | "type": "object", 61 | "properties": { 62 | "failure": { 63 | "type": "boolean" 64 | }, 65 | "rule": { 66 | "type": "string" 67 | }, 68 | "description": { 69 | "type": "string" 70 | }, 71 | "detail": { 72 | "$ref": "#/definitions/detail" 73 | }, 74 | "categories": { 75 | "type": "array", 76 | "minItems": 1, 77 | "items": { 78 | "$ref": "#/definitions/category" 79 | }, 80 | "uniqueItems": true 81 | }, 82 | "location": { 83 | "$ref": "#/definitions/location" 84 | }, 85 | "timeToFix": { 86 | "type": "integer" 87 | }, 88 | "recommendation": { 89 | "type": "string" 90 | }, 91 | "fixes": { 92 | "type": "array", 93 | "minItems": 1, 94 | "items": { 95 | "$ref": "#/definitions/fix" 96 | }, 97 | "uniqueItems": true 98 | } 99 | }, 100 | "required": [ 101 | "failure", 102 | "rule", 103 | "description" 104 | ] 105 | }, 106 | "category": { 107 | "enum": [ 108 | "Bug Risk", 109 | "Clarity", 110 | "Compatibility", 111 | "Complexity", 112 | "Duplication", 113 | "Performance", 114 | "Security", 115 | "Style" 116 | ] 117 | }, 118 | "detail": { 119 | "type": "object", 120 | "properties": { 121 | "body": { 122 | "type": "string" 123 | }, 124 | "trace": { 125 | "type": "array", 126 | "minItems": 1, 127 | "items": { 128 | "$ref": "#/definitions/location" 129 | }, 130 | "uniqueItems": true 131 | } 132 | }, 133 | "required": [ 134 | "body" 135 | ] 136 | }, 137 | "fix": { 138 | "type": "object", 139 | "properties": { 140 | "location": { 141 | "$ref": "#/definitions/location" 142 | }, 143 | "newText": { 144 | "type": "string" 145 | } 146 | }, 147 | "required": [ 148 | "location" 149 | ] 150 | }, 151 | "location": { 152 | "type": "object", 153 | "properties": { 154 | "path": { 155 | "type": "string" 156 | }, 157 | "beginLine": { 158 | "type": "integer" 159 | }, 160 | "beginColumn": { 161 | "type": "integer" 162 | }, 163 | "endLine": { 164 | "type": "integer" 165 | }, 166 | "endColumn": { 167 | "type": "integer" 168 | } 169 | }, 170 | "required": [ 171 | "path" 172 | ] 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tools/atom-linter-example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /tools/atom-linter-example/README.md: -------------------------------------------------------------------------------- 1 | # STAT package 2 | Atom package example for programs compatible with STAT. 3 | 4 | In this package we use [example-process](https://github.com/fulldecent/structured-acceptance-test/tree/master/tools/example-process) 5 | 6 | To make it works you need to install gem locally: 7 | ```bash 8 | gem build example-process.gemspec 9 | gem install example-process-0.0.1.gem 10 | ``` 11 | 12 | To run package open in Atom and run Package->Stat->Toggle (ctrl+alt+o). 13 | 14 | ![atom-package](img/stat.gif) -------------------------------------------------------------------------------- /tools/atom-linter-example/img/stat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fulldecent/structured-acceptance-test/e9710e46668163eb126692fe527a274190f979ee/tools/atom-linter-example/img/stat.gif -------------------------------------------------------------------------------- /tools/atom-linter-example/keymaps/stat.json: -------------------------------------------------------------------------------- 1 | { 2 | "atom-workspace": { 3 | "ctrl-alt-o": "stat:run" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tools/atom-linter-example/lib/stat.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | import {CompositeDisposable} from 'atom' 4 | 5 | const linterName = 'example-process' 6 | let subscriptions 7 | 8 | export function activate() { 9 | subscriptions = new CompositeDisposable() 10 | } 11 | 12 | export function deactivate() { 13 | subscriptions.dispose() 14 | } 15 | 16 | export function consumeLinter(indieRegistry) { 17 | const linter = indieRegistry.register({ 18 | name: linterName, 19 | }) 20 | subscriptions.add(linter) 21 | 22 | var exec = require('child_process').exec; 23 | var linterProcess = exec(linterName + ' ' + atom.workspace.getActiveTextEditor().getPath()); 24 | var stat = null; 25 | 26 | linterProcess.stdout.on('data', function (data) { 27 | var statObj = parseSTATJson(data); 28 | 29 | if (statObj) { 30 | if (stat) { 31 | if (!stat.findings) 32 | stat.findings = []; 33 | if (Object.prototype.toString.call(statObj) === '[object Array]') { 34 | statObj.forEach(function (el) { 35 | stat.findings.push(el); 36 | }); 37 | } 38 | else if (isFinding(statObj)) 39 | stat.findings.push(statObj); 40 | } else { 41 | linter.deleteMessages() 42 | stat = statObj; 43 | } 44 | } 45 | if (stat) { 46 | var messages = []; 47 | stat.findings.forEach(function (el) { 48 | var location = []; 49 | if (el.location) { 50 | console.log(el.location); 51 | if (el.location.beginLine && el.location.endLine) { 52 | if (el.location.beginColumn && el.location.endColumn) 53 | location = [ 54 | [el.location.beginLine, el.location.beginColumn], 55 | [el.location.endLine, el.location.endColumn] 56 | ]; 57 | else { 58 | location = [ 59 | [el.location.beginLine, 0], 60 | [el.location.endLine, 0] 61 | ]; 62 | } 63 | } 64 | } 65 | 66 | messages.push({ 67 | type: el.failure ? 'Error' : 'Warning', 68 | text: el.description, 69 | range: location, 70 | filePath: atom.workspace.getActiveTextEditor().getPath(), 71 | }); 72 | }); 73 | 74 | linter.setMessages(messages); 75 | } 76 | }); 77 | 78 | linterProcess.stderr.on('data', function (data) { 79 | console.log('stderr: ' + data.toString()); 80 | }); 81 | } 82 | 83 | function isFinding(statObj) { 84 | return statObj != null && statObj.failure != null && statObj.rule != null && statObj.description != null; 85 | } 86 | 87 | function parseSTATJson(str) { 88 | if (isJson(str = str.trim())) 89 | return JSON.parse(str); 90 | if (isJson(str = str.replace(/~+$/, ''))) 91 | return JSON.parse(str); 92 | if (isJson(str + "]}")) 93 | return JSON.parse(str + "]}"); 94 | if (isJson('{ "findings" : [' + str)) 95 | return JSON.parse('{ "findings" : [' + str).findings; 96 | return false; 97 | } 98 | 99 | function isJson(str) { 100 | try { 101 | return JSON.parse(str); 102 | } 103 | catch (e) { 104 | return false; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tools/atom-linter-example/menus/stat.json: -------------------------------------------------------------------------------- 1 | { 2 | "context-menu": { 3 | "atom-text-editor": [ 4 | { 5 | "label": "Run program", 6 | "command": "stat:run" 7 | } 8 | ] 9 | }, 10 | "menu": [ 11 | { 12 | "label": "Packages", 13 | "submenu": [ 14 | { 15 | "label": "stat", 16 | "submenu": [ 17 | { 18 | "label": "Run program", 19 | "command": "stat:run" 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tools/atom-linter-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stat", 3 | "main": "./lib/stat", 4 | "version": "0.0.0", 5 | "description": "Atom package example for programs compatible with STAT", 6 | "keywords": [ 7 | ], 8 | "activationCommands": { 9 | "atom-workspace": "stat:run" 10 | }, 11 | "repository": "https://github.com/fulldecent/structured-acceptance-test", 12 | "license": "MIT", 13 | "engines": { 14 | "atom": ">=1.0.0 <2.0.0" 15 | }, 16 | "consumedServices": { 17 | "linter-indie": { 18 | "versions": { 19 | "1.0.0": "consumeLinter" 20 | } 21 | } 22 | }, 23 | "dependencies": { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/example-consumer/stat-reader/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'structured-acceptance-test' -------------------------------------------------------------------------------- /tools/example-consumer/stat-reader/README.md: -------------------------------------------------------------------------------- 1 | # Stat Reader 2 | 3 | This script allows to read STAT format from file or from stream and produce number of failing and warnings. 4 | 5 | This explains how you can use the STAT library to consume STAT output (which is streaming JSON) and it will print output as follows: 6 | 7 | Default output: 8 | ![img](https://i.gyazo.com/5c33108b3bf2430c0baf6878a730cd16.png) 9 | 10 | Output with -s (simple output) option: 11 | ![img](https://i.gyazo.com/86038451b7eb18ca95d263690fa183c9.png) 12 | -------------------------------------------------------------------------------- /tools/example-consumer/stat-reader/stat-reader.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | require 'json-schema' 4 | require 'optparse' 5 | require 'stat' 6 | require_relative 'version' 7 | 8 | pretty_output = true 9 | 10 | def valid_json?(json) 11 | begin 12 | JSON.parse(json) 13 | return true 14 | rescue JSON::ParserError => e 15 | return false 16 | end 17 | end 18 | 19 | option = OptionParser.new do |option| 20 | option.banner = 'Usage: example-consumer ' 21 | 22 | option.on('-s', '--simple_output', 'Simple output') do 23 | pretty_output = false 24 | end 25 | 26 | option.on('-h', '--help', 'Print usage info') do 27 | puts option 28 | exit 29 | end 30 | 31 | option.on('-v', '--version', 'Print version info') do 32 | puts "example-consumer #{StatReader::VERSION}" 33 | exit 34 | end 35 | end 36 | 37 | 38 | option.parse! 39 | 40 | filename = 41 | if ARGV.length > 0 42 | ARGV[0] 43 | else 44 | if STDIN.tty? 45 | puts option 46 | exit 47 | end 48 | end 49 | 50 | String.disable_colorization = true unless pretty_output 51 | 52 | stat = nil 53 | if !filename.nil? 54 | content = File.read(filename) 55 | hash = JSON.parse(content) 56 | stat = StatModule::Stat.new(nil, hash) 57 | puts stat.process.print(pretty_output) 58 | puts 59 | stat.findings.each{ |finding| 60 | puts finding.print(pretty_output) 61 | puts 62 | } 63 | else 64 | block = '' 65 | ARGF.each_line { |line| 66 | block += line 67 | # first block - header 68 | if stat.nil? && valid_json?(block + ']}') 69 | block += ']}' 70 | stat = StatModule::Stat.new(nil, StatModule::Stat::from_json!(block)) 71 | puts stat.process.print(pretty_output) 72 | puts 73 | block = '' 74 | else 75 | # second block - findings 76 | if !stat.nil? && valid_json?(block.chomp(",\n")) 77 | finding = StatModule::Finding.new(nil, nil, nil, StatModule::Finding::from_json!(block.chomp(",\n"))) 78 | stat.findings.push(finding) 79 | puts finding.print(pretty_output) 80 | puts 81 | block = '' 82 | end 83 | end 84 | 85 | STDOUT.flush 86 | } 87 | end 88 | 89 | unless stat.nil? 90 | puts stat.summary_print(pretty_output) 91 | end -------------------------------------------------------------------------------- /tools/example-consumer/stat-reader/version.rb: -------------------------------------------------------------------------------- 1 | module StatReader 2 | VERSION = '0.1' 3 | end 4 | -------------------------------------------------------------------------------- /tools/example-process/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'structured-acceptance-test' -------------------------------------------------------------------------------- /tools/example-process/README.md: -------------------------------------------------------------------------------- 1 | # Example process 2 | 3 | This script emulate STAT findings stream. 4 | -------------------------------------------------------------------------------- /tools/example-process/bin/example-process: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | gem 'structured-acceptance-test', '0.0.5' 3 | require 'stat' 4 | 5 | process = StatModule::Process.new('One two three example process', nil) 6 | stat = StatModule::Stat.new(process) 7 | 8 | finding_one = StatModule::Finding.new(false, 'Go to your room', 'I told you once') 9 | location_one = StatModule::Location.new('somefile') 10 | location_one.begin_line = 5 11 | location_one.end_line = 5 12 | location_one.begin_column = 1 13 | location_one.end_column = 6 14 | finding_one.location = location_one 15 | 16 | finding_two = StatModule::Finding.new(false, 'Go to your room', 'I told you twice') 17 | location_two = StatModule::Location.new('somefile') 18 | location_two.begin_line = 8 19 | location_two.end_line = 8 20 | location_two.begin_column = 2 21 | location_two.end_column = 10 22 | finding_two.location = location_two 23 | 24 | finding_three = StatModule::Finding.new(true, 'Go to your room', 'I told you three times') 25 | location_three = StatModule::Location.new('somefile') 26 | location_three.begin_line = 4 27 | location_three.end_line = 5 28 | location_three.begin_column = 1 29 | location_three.end_column = 11 30 | finding_three.location = location_three 31 | 32 | stat.print_header 33 | sleep 1 34 | 35 | stat.findings.push(finding_one) 36 | stat.print_finding 37 | sleep 3 38 | 39 | stat.findings.push(finding_two) 40 | stat.print_finding 41 | sleep 3 42 | 43 | stat.findings.push(finding_three) 44 | stat.print_finding 45 | sleep 3 46 | 47 | stat.print_footer 48 | -------------------------------------------------------------------------------- /tools/example-process/example-process.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo '{' 4 | echo ' "statVersion": "1.0.0",' 5 | echo ' "process": {' 6 | echo ' "name": "One two three example process",' 7 | echo ' "version": "1"' 8 | echo ' },' 9 | echo ' "findings": [' 10 | sleep 1 11 | echo ' {' 12 | echo ' "failure": false,' 13 | echo ' "rule": "Go to your room",' 14 | echo ' "description": "I told you zero times"' 15 | echo ' },' 16 | sleep 1 17 | echo ' {' 18 | echo ' "failure": false,' 19 | echo ' "rule": "Go to your room",' 20 | echo ' "description": "I told you once",' 21 | echo ' "location": {' 22 | echo ' "path": "/some/path.txt",' 23 | echo ' "beginLine": 1,' 24 | echo ' "endLine": 2' 25 | echo ' }' 26 | echo ' },' 27 | sleep 2 28 | echo ' {' 29 | echo ' "failure": false,' 30 | echo ' "rule": "Go to your room",' 31 | echo ' "description": "I told you twice",' 32 | echo ' "location": {' 33 | echo ' "path": "/some/path.txt",' 34 | echo ' "beginLine": 1,' 35 | echo ' "endLine": 2,' 36 | echo ' "beginColumn": 23,' 37 | echo ' "endColumn": 29' 38 | echo ' }' 39 | echo ' },' 40 | sleep 3 41 | echo ' {' 42 | echo ' "failure": true,' 43 | echo ' "rule": "Go to your room",' 44 | echo ' "description": "I told you three times",' 45 | echo ' "recommendation": "Reword to avoid the passive voice.",' 46 | echo ' "location": {' 47 | echo ' "path": "/some/path.txt",' 48 | echo ' "beginLine": 1,' 49 | echo ' "endLine": 1,' 50 | echo ' "beginColumn": 23,' 51 | echo ' "endColumn": 29' 52 | echo ' }' 53 | echo ' }' 54 | sleep 1 55 | echo ' ]' 56 | echo '}' 57 | -------------------------------------------------------------------------------- /tools/example-process/example-process.gemspec: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | Gem::Specification.new do |s| 3 | s.name = 'example-process' 4 | s.version = '0.0.1' 5 | s.date = '2017-02-15' 6 | s.summary = 'example-process' 7 | s.description = 'example-process for STAT' 8 | s.authors = ['William Entriken', 'Ilia Grabko'] 9 | s.email = 'github.com@phor.net' 10 | s.homepage = 'https://github.com/fulldecent/structured-acceptance-test' 11 | s.license = 'MIT' 12 | s.executables << 'example-process' 13 | end -------------------------------------------------------------------------------- /tools/example-process/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "statVersion": "1.0.0", 3 | "process": { 4 | "name": "One two three example process", 5 | "version": "1" 6 | }, 7 | "findings": [ 8 | { 9 | "failure": false, 10 | "rule": "Go to your room", 11 | "description": "I told you zero times" 12 | }, 13 | { 14 | "failure": false, 15 | "rule": "Go to your room", 16 | "description": "I told you once", 17 | "location": { 18 | "path": "/some/path.txt", 19 | "beginLine": 1, 20 | "endLine": 2 21 | } 22 | }, 23 | { 24 | "failure": false, 25 | "rule": "Go to your room", 26 | "description": "I told you twice", 27 | "location": { 28 | "path": "/some/path.txt", 29 | "beginLine": 1, 30 | "endLine": 2, 31 | "beginColumn": 23, 32 | "endColumn": 29 33 | } 34 | }, 35 | { 36 | "failure": true, 37 | "rule": "Go to your room", 38 | "description": "I told you three times", 39 | "recommendation": "Reword to avoid the passive voice.", 40 | "location": { 41 | "path": "/some/path.txt", 42 | "beginLine": 1, 43 | "endLine": 1, 44 | "beginColumn": 23, 45 | "endColumn": 29 46 | } 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /tools/junit-stat-converter/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'xml-simple' 4 | gem 'structured-acceptance-test' -------------------------------------------------------------------------------- /tools/junit-stat-converter/README.md: -------------------------------------------------------------------------------- 1 | # jUnit to STAT converter 2 | 3 | This tool allows you convert [jUnit format](http://help.catchsoftware.com/display/ET/JUnit+Format) to STAT. 4 | 5 | ## Usage 6 | ruby junit-stat-converter \ 7 | 8 | STAT generates by next rules: 9 | * Process name is always 'JUnit' 10 | * Each testcase has corresponding finding 11 | * If test failed finding marks as failed 12 | * The rule of finding is the name of testcase 13 | * Description is failure message -------------------------------------------------------------------------------- /tools/junit-stat-converter/features/run_example_tests.feature: -------------------------------------------------------------------------------- 1 | Feature: Run example tests 2 | 3 | Scenario: Running example tests 4 | Given the program has finished 5 | Then the output is correct for each test 6 | -------------------------------------------------------------------------------- /tools/junit-stat-converter/features/steps/junit1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tools/junit-stat-converter/features/steps/junit2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Assertion failed 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tools/junit-stat-converter/features/steps/steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^the program has finished$/) do 2 | # Test files are generated using iconv. 3 | 4 | @cucumber = `ruby junit-stat-converter.rb features/steps/junit1.xml` 5 | @cucumber2 = `ruby junit-stat-converter.rb features/steps/junit2.xml` 6 | end 7 | 8 | Then(/^the output is correct for each test$/) do 9 | stat = JSON.parse(@cucumber) 10 | expect(stat['statVersion']).to match('1.0.0') 11 | 12 | stat = JSON.parse(@cucumber2) 13 | expect(stat['statVersion']).to match('1.0.0') 14 | end 15 | -------------------------------------------------------------------------------- /tools/junit-stat-converter/junit-stat-converter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require_relative 'version' 5 | require_relative 'stat' 6 | require 'xmlsimple' 7 | 8 | option = OptionParser.new do |option| 9 | option.banner = 'Usage: junit-stat-converter ' 10 | 11 | option.on('-h', '--help', 'Print usage info') do 12 | puts option 13 | exit 14 | end 15 | 16 | option.on('-v', '--version', 'Print version info') do 17 | puts "junit-stat-converter #{JUnitStatConverter::VERSION}" 18 | exit 19 | end 20 | end 21 | 22 | option.parse! 23 | 24 | filename = 25 | if ARGV == [] 26 | puts option 27 | exit 28 | else 29 | ARGV[0] 30 | end 31 | 32 | hash = XmlSimple.xml_in(filename) 33 | 34 | process = StatModule::Process.new('JUnit') 35 | stat = StatModule::Stat.new(process) 36 | findings = [] 37 | 38 | hash['testsuite'].each { |testsuite| 39 | testsuite['testcase'].each { |testcase| 40 | failure = false 41 | rule = testcase['name'] 42 | description = '' 43 | unless testcase['failure'].nil? 44 | failure = true 45 | unless ['failure'].nil? 46 | description = testcase['failure'].first['message'] unless ['failure'].first.nil? 47 | end 48 | end 49 | finding = StatModule::Finding.new(failure, rule, description) 50 | findings.push(finding) 51 | } unless testsuite['testcase'].nil? 52 | } 53 | 54 | stat.findings = findings 55 | puts stat.to_json 56 | 57 | -------------------------------------------------------------------------------- /tools/junit-stat-converter/version.rb: -------------------------------------------------------------------------------- 1 | module JUnitStatConverter 2 | VERSION = '0.1' 3 | end 4 | -------------------------------------------------------------------------------- /tools/stat-validator/stat-validator.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require "json-schema" 5 | require 'optparse' 6 | require_relative 'version' 7 | 8 | option = OptionParser.new do |option| 9 | option.banner = 'Usage: stat-validator ' 10 | 11 | option.on('-h', '--help', 'Print usage info') do 12 | puts option 13 | exit 14 | end 15 | 16 | option.on('-v', '--version', 'Print version info') do 17 | puts "stat-validator #{StatValidator::VERSION}" 18 | exit 19 | end 20 | end 21 | 22 | option.parse! 23 | 24 | filename = 25 | if ARGV == [] 26 | puts option 27 | exit 28 | else 29 | ARGV[0] 30 | end 31 | schema_path = File.dirname(__FILE__) + '/../../statJsonSchema.json' 32 | 33 | puts JSON::Validator.fully_validate(schema_path, filename, :uri => true) 34 | -------------------------------------------------------------------------------- /tools/stat-validator/version.rb: -------------------------------------------------------------------------------- 1 | module StatValidator 2 | VERSION = '0.1' 3 | end 4 | --------------------------------------------------------------------------------