{"id":19736118,"url":"https://github.com/arnauld/tzatziki","last_synced_at":"2025-04-30T04:31:57.658Z","repository":{"id":19063114,"uuid":"22289790","full_name":"Arnauld/tzatziki","owner":"Arnauld","description":"Various utilities for Cucumber: grammar extraction, tags, execution report, ...","archived":false,"fork":false,"pushed_at":"2018-05-15T11:33:25.000Z","size":1874,"stargazers_count":33,"open_issues_count":4,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-05T22:31:56.348Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://arnauld.github.io/tzatziki","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Arnauld.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-26T15:38:14.000Z","updated_at":"2023-10-04T07:59:38.000Z","dependencies_parsed_at":"2022-07-23T02:16:23.171Z","dependency_job_id":null,"html_url":"https://github.com/Arnauld/tzatziki","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Ftzatziki","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Ftzatziki/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Ftzatziki/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Ftzatziki/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Arnauld","download_url":"https://codeload.github.com/Arnauld/tzatziki/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251643046,"owners_count":21620413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T01:05:22.435Z","updated_at":"2025-04-30T04:31:57.225Z","avatar_url":"https://github.com/Arnauld.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"tzatziki\n--------\n\n* [Getting started](#getting-started)\n  * [Maven dependency](#maven-dependency)\n  * [First generate the execution report](#first-generate-the-execution-report)\n  * [Generate a report based on the execution file](#generate-a-report-based-on-the-execution-file)\n  * [Plug everything together](#plug-everything-together)\n* [A simple preamble](#a-simple-preamble)\n* [A feature file](#a-feature-file)\n* [Extended markdown](#extended-markdown)\n* [Test settings](#test-settings)\n* [Tag Dictionary](#tag-dictionary)\n  * [Add a sanity check on tags](#add-a-sanity-check-on-tags)\n\n\nA complete example of a pdf report generated using tzatziki can be found here [Sample PDF](doc/sample-yamex-report.pdf)\n\n\n## Getting started\n\n### Maven dependency\n\nLast released version: `0.16.0`\n\n**For pdf reporting:**\n\n```\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.technbolts.tzatziki\u003c/groupId\u003e\n            \u003cartifactId\u003etzatziki-pdf\u003c/artifactId\u003e\n            \u003cversion\u003e${tzatziki.version}\u003c/version\u003e\n        \u003c/dependency\u003e\n```\n\n\n### First generate the execution report\n\nThis can be achieved by adding the tzatziki reporter that will track all information during cucumber execution.\nReporter will then create an execution file: `target/myapp/exec.json`\n\n```java\n\nimport cucumber.api.CucumberOptions;\nimport cucumber.api.junit.Cucumber;\nimport org.junit.runner.RunWith;\n\n@RunWith(Cucumber.class)\n@CucumberOptions(\n        strict = true,\n        tags = {\"~@wip\", \"~@notImplemented\"},\n        format = \"tzatziki.analysis.exec.gson.JsonEmitterReport:target/myapp\")\npublic class RunAllFeatures {\n}\n\n```\n\n### Generate a report based on the execution file\n\nWhereas this may seem a bit complicated, this is actually really simple.\n\n1. Read the execution file (`myapp/exec.json`) back to the tzatziki model of execution (`List\u003cFeatureExec\u003e`)\n2. Indicate the output file; (see [TestSettings](#test-settings))\n3. Configure the report using the basic builder\n    * declare the `imageDir` variable used in the markdown image path definition (see [preamble.md](#a-simple-preamble-md)); this will be replaced within markdown file to complete image path. You can use whatever variable name you want\n    * Main title of the report and its sub-title; they only appear in the default first page renderer\n\nThen in order, the following content will be included in the document:\n\n1. Include the markdown file named `preamble.md`\n2. All features\n3. Sample steps used as a legend for icon\n\n```java\npackage myapp.feature;\n\nimport com.itextpdf.text.DocumentException;\nimport gutenberg.itext.FontModifier;\nimport gutenberg.itext.Styles;\nimport gutenberg.itext.model.Markdown;\nimport org.apache.commons.io.IOUtils;\nimport tzatziki.analysis.exec.gson.JsonIO;\nimport tzatziki.analysis.exec.model.FeatureExec;\nimport tzatziki.pdf.support.Configuration;\nimport tzatziki.pdf.support.DefaultPdfReportBuilder;\nimport myapp.TestSettings;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\n/**\n * @author \u003ca href=\"http://twitter.com/aloyer\"\u003e@aloyer\u003c/a\u003e\n */\npublic class PdfSimpleReport {\n    public void generate() throws IOException, DocumentException {\n        List\u003cFeatureExec\u003e execs = loadExec(new File(buildDir(), \"myapp/exec.json\"));\n\n        File fileOut = new File(buildDir(), \"myapp/report.pdf\");\n\n        new DefaultPdfReportBuilder()\n                .using(new Configuration()\n                                .displayFeatureTags(true)\n                                .displayScenarioTags(true)\n                                .declareProperty(\"imageDir\",\n                                        new File(baseDir(), \"/src/test/resources/myapp/feature/images\").toURI().toString())\n                                .adjustFont(Styles.TABLE_HEADER_FONT, new FontModifier().size(10.0f))\n                )\n                .title(\"myapp\")\n                .subTitle(\"Technical \u0026 Functional specifications\")\n                .markup(Markdown.fromUTF8Resource(\"/myapp/feature/preamble.md\"))\n                .features(execs)\n                .sampleSteps()\n                .generate(fileOut);\n    }\n\n    private static File buildDir() {\n        String baseDir = new TestSettings().getBuildDir();\n        return new File(baseDir);\n    }\n\n    private static File baseDir() {\n        String baseDir = new TestSettings().getBaseDir();\n        return new File(baseDir);\n    }\n\n    private static List\u003cFeatureExec\u003e loadExec(File file) throws IOException {\n        InputStream in = null;\n        try {\n            in = new FileInputStream(file);\n            return new JsonIO().load(in);\n        } finally {\n            IOUtils.closeQuietly(in);\n        }\n    }\n}\n```\n\n### Plug everything together\n\nThe simpler way to plug everything together is to use junit suite:\n\nOnce cucumber is done, trigger the report generation (`@AfterClass`)\n\n\n```java\nimport org.junit.AfterClass;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Suite;\n\n/**\n * @author \u003ca href=\"http://twitter.com/aloyer\"\u003e@aloyer\u003c/a\u003e\n */\n@RunWith(Suite.class)\n@Suite.SuiteClasses({RunAllFeatures.class})\npublic class RunAllFeatureAndGenerateReportTest {\n\n    @AfterClass\n    public static void generateExecutionReport() throws Exception {\n        new PdfSimpleReport().generate();\n    }\n}\n```\n\n\n## A more advanced report\n\nTODO\n\n\n## A simple preamble\n\n\nThis preamble illustrate several markdown enhancement supported by tzatziki (though [gutenberg](https://github.com/Arnauld/gutenberg)).\n\n\n```markdown\n    # Welcome to Yamex!\n\n    ![Yamex Logo](${imageDir}/Yamex-header.png)\n\n    Yamex is the next-gen market exchange engine that will probably drives all EURO, AMER and ASIA trading platform.\n\n    A central part of a market exchange engine is the matching engine.\n    A matching engine is a program that accepts orders from buyers and sellers. The other matching module matches buy and sell orders, creates transactions to record the process, and updates the customers account balances.\n\n    The matching (or trade allocation) algorithm is an important part of an exchange trading mechanism, since it is responsible for resolving the buy/sell association in a fast and efficient way.\n\n    Exchanges set the institutional rules that govern trading and information flows about that trading. They are closely linked to the clearing facilities through which post-trade activities could be completed. An exchange centralizes the communication of bid and offer prices to all direct market participants, who can respond by selling or buying at one of the quotes or by replying with a different quote.\n\n    {icon=warning,icon-color=dark-red}\n    G\u003e This specification describes the matching engine expected behaviors.\n\n    # Terminology\n\n    * **Order** - An order is an instruction to buy or sell a stock at a specific price or better\n    * **Bid** - the price in a buy order\n    * **Ask** - the price in a sell order\n    * **Spread** - the difference between the bid and the ask\n    * **Time In Force** - indicates how long an order will remain active (see Appendix B)\n\n\n    ## Design Considerations\n\n    ```ditaa\n                                                                 +-------------+\n                                                         /------ | Market Book |\n    /--------------\\     submit order  /-------------\\   |       +-------------+\n    | Electronic   | ----------------\u003e | Transaction |---/              .\n    | Trading      |                   | Router      |------ ...        .\n    | Network cBLK | \u003c---------------- |        cRED |---\\              .\n    \\--------------/   order status    \\-------------/   |       +-------------+\n                                                         \\-------| Market Book |\n                                                                 +-------------+\n    ```\n\n    ### Price/Time Algorithm\n\n    Under the Price/Time algorithm, an incoming trade is matched with the first orders (at the best price), which is the so called FIFO method.\n    More precisely: suppose `n` limit orders `Q1` ... `Qn` of a cumulative volume `Q` lots, and an incoming trade of `N ≤ Q` lots.\n\n    Then there exists an index `1 ≤ j ≤ n` such that:\n\n    ```formula\n    \\sum\\limits_{i=1}^j Q_i \\leq N\n    ```\n\n    and\n\n    ```formula\n    \\sum\\limits_{i=1}^{min(j+1,n)} Q_i \\geq N\n    ```\n\n    Under the Price/Time algorithm, the first `j` limit orders are filled in full, and the remaining lots are assigned to the `(j+1)`-th limit order (if `j \u003c n`)\n```\n\n## A Feature File\n\nDue to cucumber issue while parsing markdown (`*` is a bullet in markdown but considered as a step in cucumber);\nthe easiest way to include markdown within your scenario is to comment them.\nDouble comment (`##`) can then be used to comment out information that won't be rendered in the final report.\n\nYou can note several extensions within the markdown:\n\n* [Icon based block](#icon-based-block)\n\n```\n   #{icon=info-circle, icon-color=#00b200}\n   #G\u003e In our system we support Limit Orders with various time in force parameters.\n   #G\u003e Fill-or-Kill (FoK), Immediate-or-Cancel (IoC) works the same way as for Market Orders.\n   #G\u003e Good-Till Date or Good-Till-Cancel and Day Orders are valid until specified time\n   #G\u003e requested by investor and cancelled after that.\n```\n\n* [Syntax highlighting](#syntax-highlighting)\n\n```\n   #```cucumber\n   #  Given an order book containing two sell orders: 15@10.4 by B1 and 150@11.9 by B2\n   #  When a buy order is placed 20@10.4 by BBuyer\n   #  Then the buy order should remain in the order book with the remaining quantity of 5@10.4\n   #  But an execution should have been triggered: [B1-\u003eBBuyer 15@10.4]\n   #```\n```\n\n\n```gherkin\nFeature: Limit Order\n\n#  A limit order is an order to buy or sell a contract at a specific price or better.\n#\n#  A **buy** limit order can only be executed at the **limit price or lower**, and\n#  a **sell** limit order can only be executed at the **limit price or higher**.\n#\n#  Use of a Limit order helps ensure that the customer will not receive an execution\n#  at a price less favorable than the limit price. Use of a Limit order, however,\n#  does not guarantee an execution.\n#\n#  * A buy limit order for FFLY at $125 will buy shares of FFLY at $125 or less.\n#  * A sell limit order for FFLY at $125 will sell shares of FFLY for $125 or more.\n#  * A limit order must have a Time in Force (TIF) value\n#\n#{icon=info-circle, icon-color=#00b200}\n#G\u003e In our system we support Limit Orders with various time in force parameters.\n#G\u003e Fill-or-Kill (FoK), Immediate-or-Cancel (IoC) works the same way as for Market Orders.\n#G\u003e Good-Till Date or Good-Till-Cancel and Day Orders are valid until specified time\n#G\u003e requested by investor and cancelled after that.\n\n  @limitOrder @placeOrder\n  Scenario: Place a Buy Limit Order\n\n    Given an empty order book\n    When a limit order is placed to buy 150 FFLY at 10.4€\n    Then the order book should be updated with this new order\n\n  @orderBook @bestPrices @limitOrder @stopOrder\n  Scenario: Best ask price - limit orders and stop orders\n\n    #{icon=warning, icon-color=dark-red}\n    #G\u003e  Stop order should not be taken into account for best price.\n    #\n\n    Given an empty order book\n    And the following orders have been placed:\n      | instrument | order type  | way  | qty | price |\n      | FFLY       | Stop Order  | Sell | 150 | 10.1  |\n      | FFLY       | Limit Order | Sell | 150 | 11.9  |\n      | FFLY       | Limit Order | Sell | 15  | 10.4  |\n      | FFLY       | Limit Order | Buy  | 15  | 10.0  |\n    Then the order book's best ask price should be 10.4€\n\n  @orderBook @matchingPrinciple @limitOrder\n  Scenario: Matching a Buy order partially - exact same price\n\n    #\n    # Order book contains already two sell orders.\n    #\n    # The buy order triggered is not fully fulfilled, thus remains in the order book but with only the missing quantity.\n    #\n    # But an execution is triggered for the partial fulfillment\n    #\n    # **Alternate scenario?**\n    #\n    #{width:100%}\n    #```cucumber\n    #  Given an order book containing two sell orders: 15@10.4 by B1 and 150@11.9 by B2\n    #  When a buy order is placed 20@10.4 by BBuyer\n    #  Then the buy order should remain in the order book with the remaining quantity of 5@10.4\n    #  But an execution should have been triggered: [B1-\u003eBBuyer 15@10.4]\n    #```\n\n    Given an empty order book\n    And the following orders have been placed:\n      | Broker | order type  | way  | qty | price |\n      | B1     | Limit Order | Sell | 15  | 10.4  |\n      | B2     | Limit Order | Sell | 150 | 11.9  |\n    When a limit order is placed to buy 20 FFLY at 10.4€ by \"Broker-A\"\n\n    Then the order book should be composed of the following orders:\n      | Broker   | order type  | way  | qty | price |\n      | Broker-A | Limit Order | Buy  | 5   | 10.4  |\n      | B2       | Limit Order | Sell | 150 | 11.9  |\n    And the following execution should have been triggered:\n      | seller broker | buyer broker | qty | price |\n      | B1            | Broker-A     | 15  | 10.4  |\n\n```\n\n## Extended markdown\n\n* [variable reference in URL](variable-reference-in-url)\n* [Icon based block](#icon-based-block)\n* [Syntax highlighting](#syntax-highlighting)\n* [Ditaa support](#ditaa-support)\n* [Math LateX support](#math-latex-support)\n\n### variable reference in URL\n\ne.g. `${imageDir}` in `![Yamex Logo](${imageDir}/Yamex-header.png)`\n\n### Icon based block\n\n```\n  {icon=warning,icon-color=dark-red}\n  G\u003e This specification describes the matching engine expected behaviors.\n```\n\nis rendered as:\n\n![Warning block](doc/images/warning-block.png)\n\n```\n  #{icon=info-circle, icon-color=#00b200}\n  #G\u003e In our system we support Limit Orders with various time in force parameters.\n  #G\u003e Fill-or-Kill (FoK), Immediate-or-Cancel (IoC) works the same way as for Market Orders.\n  #G\u003e Good-Till Date or Good-Till-Cancel and Day Orders are valid until specified time\n  #G\u003e requested by investor and cancelled after that.\n```\n\nis rendered as:\n\n![Info block](doc/images/info-block.png)\n\n\n\nList of available icons can be found here: [Font-Awesome](http://fortawesome.github.io/Font-Awesome/icons/)\n\n### Syntax highlighting\n\nCode blocks can be taken a step further by adding syntax highlighting.\nIn your fenced block, add an optional language identifier and we'll run it through syntax highlighting.\n\nCurrently supported language are available here: [Pygments](http://pygments.org/languages/)\n\n### Ditaa support\n\n[Ditaa](http://ditaa.sourceforge.net/) block is also supported as an extra language;\nit can be triggered using the triple ticks block marker followed by the `ditaa` language.\n\n```\n    ```ditaa\n                                                                     +-------------+\n                                                             /------ | Market Book |\n        /--------------\\     submit order  /-------------\\   |       +-------------+\n        | Electronic   | ----------------\u003e | Transaction |---/              .\n        | Trading      |                   | Router      |------ ...        .\n        | Network cBLK | \u003c---------------- |        cRED |---\\              .\n        \\--------------/   order status    \\-------------/   |       +-------------+\n                                                             \\-------| Market Book |\n                                                                     +-------------+\n    ```\n```\n\nis rendered as:\n\n\n![Ditaa support](doc/images/ditaa-support.png)\n\n### Math LateX support\n\n[JLatexMath](http://forge.scilab.org/index.php/p/jlatexmath/) block is also supported as an extra language;\nit can be triggered using the triple ticks block marker followed by the `formula` language.\n\n\n```\n    ```formula\n    \\sum\\limits_{i=1}^{min(j+1,n)} Q_i \\geq N\n    ```\n```\n\nis rendered as:\n\n![JLatexMath support](doc/images/jlatexmath-support.png)\n\n\n\n## Test settings\n\nSimply provide settings, project directory, resource path and so on...\n\nThe snippet provided are a basic usage of both property file and maven resource filtering.\n\n`myapp/test-settings.properties`\n\n```\nbuildDir=${project.build.directory}\nbaseDir=${project.basedir}\n```\n\n\n`pom.xml`\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n\n    ...\n\n    \u003cbuild\u003e\n        \u003ctestResources\u003e\n            \u003ctestResource\u003e\n                \u003cdirectory\u003esrc/test/resources\u003c/directory\u003e\n                \u003cfiltering\u003etrue\u003c/filtering\u003e\n                \u003cincludes\u003e\n                    \u003cinclude\u003e**/*.properties\u003c/include\u003e\n                \u003c/includes\u003e\n            \u003c/testResource\u003e\n            \u003ctestResource\u003e\n                \u003cdirectory\u003esrc/test/resources\u003c/directory\u003e\n                \u003cfiltering\u003efalse\u003c/filtering\u003e\n                \u003cexcludes\u003e\n                    \u003cexclude\u003e**/*.properties\u003c/exclude\u003e\n                \u003c/excludes\u003e\n            \u003c/testResource\u003e\n        \u003c/testResources\u003e\n\n        ...\n\n    \u003c/build\u003e\n\u003c/project\u003e\n```\n\n`myapp.TestSettings`\n\n```java\npublic class TestSettings {\n\n    private Properties properties;\n\n    public TestSettings() {\n    }\n\n    public String getBuildDir() {\n        return getProperties().getProperty(\"buildDir\");\n    }\n\n    public String getBaseDir() {\n        return getProperties().getProperty(\"baseDir\");\n    }\n\n    public Properties getProperties() {\n        if (properties == null) {\n            properties = new Properties();\n            InputStream stream = null;\n            try {\n                stream = getClass().getResourceAsStream(\"/myapp/test-settings.properties\");\n                properties.load(stream);\n            } catch (IOException e) {\n                throw new RuntimeException(\"Failed to open settings\", e);\n            } finally {\n                IOUtils.closeQuietly(stream);\n            }\n        }\n        return properties;\n    }\n}\n```\n\n## Tag Dictionary\n\nBy using a tag dictionary, you can ensure everyone is using the same tags.\nChecks can also be easily added to ensure only tags defined within the dictionary are used within the feature files.\nThis prevents misspell by raising an error.\n\nAn example of `tags.properties`\n\n```INI\nwip=Work in progress\nnotImplemented=Behavior not implemented\n\ndefault=Define standard and predefined objects with basic characteristics (e.g. limit order...)\n\norderBook=Order Book\n\nlimitOrder=Limit Order\nmarketOrder=Market Order\nstopOrder=Stop 'Loss' Order\n\ntimeInForce=Time In Force\n\nplaceOrder=Order placed in order book\nbestPrices=Order book query to retrieve actual best prices (best bid or ask)\ncumulativeView=Order book cumulative view\nmatchingPrinciple=Order book's Matching principles\n\n```\n\n### Add a sanity check on tags\n\n```java\n\nimport org.junit.runner.RunWith;\nimport samples.TestSettings;\nimport tzatziki.analysis.step.Features;\nimport tzatziki.analysis.tag.TagDictionary;\nimport tzatziki.junit.SanityTagChecker;\n\nimport java.io.File;\n\nimport static tzatziki.junit.SanityTagChecker.loadFeaturesFromSourceDirectory;\n\n/**\n * @author \u003ca href=\"http://twitter.com/aloyer\"\u003e@aloyer\u003c/a\u003e\n */\n@RunWith(SanityTagChecker.class)\npublic class CoffeeMachineTagCheckTest {\n\n    @SanityTagChecker.TagDictionaryProvider\n    public static TagDictionary tagDictionary() {\n        return new TagDictionary()\n                .declareTag(\"@wip\")\n                .declareTag(\"@protocol\")\n                .declareTag(\"@notification\")\n                .declareTag(\"@message\")\n                .declareTag(\"@runningOut\")\n                .declareTag(\"@coffee\")\n                .declareTag(\"@tea\")\n                .declareTag(\"@chocolate\")\n                .declareTag(\"@sugar\")\n                .declareTag(\"@noSugar\")\n                .declareTag(\"@takeOrder\")\n                .declareTag(\"@payment\")\n                .declareTag(\"@reporting\")\n                .declareTag(\"@manual\")\n                ;\n    }\n\n    @SanityTagChecker.FeaturesProvider\n    public static Features features() {\n        String basedir = new TestSettings().getBaseDir();\n        return loadFeaturesFromSourceDirectory(new File(basedir, \"src/main/resources/samples/coffeemachine\"));\n    }\n}\n```\n\n### Check extension\nDefault check ensures all tags exist in the dictionary. If you want to\nassess that you cover a full perimeter you can use a distinct TagChecker\n\n```java\n\n    @SanityTagChecker.TagDictionaryProvider\n    public static TagDictionary tagDictionary() {\n        return new TagDictionary()\n                .declareTag(\"@coffee\")\n                .declareTag(\"@tea\")\n                .declareTag(\"@chocolate\")\n                .declareTag(\"@orangeJuice\")\n                .declareTag(\"@noDrink\")\n                ;\n    }\n\n    @SanityTagChecker.FeaturesProvider\n    public static Features features() {\n        String basedir = new TestSettings().getBaseDir();\n        return loadFeaturesFromSourceDirectory(new File(basedir, \"src/main/resources/samples/coffeemachine\"));\n    }\n\n    @SanityTagChecker.TagCheckerProvider\n    public static TagChecker checker() {\n        return new CheckAtLeastOneTagsExist();\n    }\n\n    @SanityTagChecker.CheckScopeProvider\n    public static Set\u003cCucumberPart\u003e scope() {\n        return EnumSet.of(CucumberPart.Scenario);\n    }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnauld%2Ftzatziki","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnauld%2Ftzatziki","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnauld%2Ftzatziki/lists"}