com.github.rvesse.airline.TestHelp.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rvesse.airline.TestHelp.java

Source

/**
 * Copyright (C) 2010-16 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.rvesse.airline;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.github.rvesse.airline.Cli;
import com.github.rvesse.airline.SingleCommand;
import com.github.rvesse.airline.builder.CliBuilder;
import com.github.rvesse.airline.Git.Add;
import com.github.rvesse.airline.Git.RemoteAdd;
import com.github.rvesse.airline.Git.RemoteShow;
import com.github.rvesse.airline.args.Args1;
import com.github.rvesse.airline.args.Args2;
import com.github.rvesse.airline.args.ArgsAllowedValues;
import com.github.rvesse.airline.args.ArgsArityString;
import com.github.rvesse.airline.args.ArgsBooleanArity;
import com.github.rvesse.airline.args.ArgsCopyrightAndLicense;
import com.github.rvesse.airline.args.ArgsExamples;
import com.github.rvesse.airline.args.ArgsExitCodes;
import com.github.rvesse.airline.args.ArgsHiddenDiscussion;
import com.github.rvesse.airline.args.ArgsInherited;
import com.github.rvesse.airline.args.ArgsInheritedDiscussion;
import com.github.rvesse.airline.args.ArgsMultiLineDescription;
import com.github.rvesse.airline.args.ArgsMultiParagraphDiscussion;
import com.github.rvesse.airline.args.ArgsRequired;
import com.github.rvesse.airline.args.ArgsRestoredDiscussion;
import com.github.rvesse.airline.args.ArgsVersion;
import com.github.rvesse.airline.args.ArgsVersion2;
import com.github.rvesse.airline.args.ArgsVersion3;
import com.github.rvesse.airline.args.ArgsVersionMissing;
import com.github.rvesse.airline.args.ArgsVersionMissingSuppressed;
import com.github.rvesse.airline.args.CommandHidden;
import com.github.rvesse.airline.args.GlobalOptionsHidden;
import com.github.rvesse.airline.args.OptionsHidden;
import com.github.rvesse.airline.args.OptionsRequired;
import com.github.rvesse.airline.command.CommandRemove;
import com.github.rvesse.airline.help.Help;
import com.github.rvesse.airline.help.UsageHelper;
import com.github.rvesse.airline.help.cli.CliCommandUsageGenerator;
import com.github.rvesse.airline.help.cli.CliGlobalUsageSummaryGenerator;
import com.github.rvesse.airline.help.common.AbstractCommandUsageGenerator;
import com.github.rvesse.airline.model.CommandMetadata;
import com.github.rvesse.airline.restrictions.partial.PartialAnnotated;
import com.github.rvesse.airline.utils.AirlineUtils;
import com.github.rvesse.airline.utils.predicates.parser.CommandFinder;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

import static com.github.rvesse.airline.SingleCommand.singleCommand;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

@Test
@SuppressWarnings("unchecked")
public class TestHelp {
    private final Charset utf8 = Charset.forName("utf-8");

    /**
     * Helper method for if you're trying to determine the differences between
     * actual and expected output when debugging a new test and can't visually
     * see the difference e.g. differing white space
     * 
     * @param actual
     *            Actual
     * @param expected
     *            Expected
     */
    private void testStringAssert(String actual, String expected) {
        if (!actual.equals(expected)) {
            if (actual.length() != expected.length()) {
                System.err.println(
                        "Different lengths, expected " + expected.length() + " but got " + actual.length());
            }
            for (int i = 0; i < expected.length(); i++) {
                char e = expected.charAt(i);
                if (i >= actual.length()) {
                    System.err.println("Expected character '" + e + "' (Code " + (int) e + ") is at position " + i
                            + " which is beyond the length of the actual string");
                    break;
                }
                char a = actual.charAt(i);
                if (e != a) {
                    System.err.println("Expected character '" + e + "' (Code " + (int) e + ") at position " + i
                            + " does not match actual character '" + a + "' (Code " + (int) a + ")");
                    int start = Math.max(0, i - 10);
                    int end = Math.min(expected.length(), i + 10);
                    System.err.println("Expected Context:");
                    System.err.println(expected.substring(start, end));
                    System.err.println("Actual Context:");
                    end = Math.min(actual.length(), i + 10);
                    System.err.println(actual.substring(start, end));
                    break;
                }
            }
        }
        assertEquals(actual, expected);
    }

    public void testMultiLineDescriptions() throws IOException {
        SingleCommand<ArgsMultiLineDescription> cmd = singleCommand(ArgsMultiLineDescription.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        ArgsMultiLineDescription - Has\n" + "        some\n" + "        new lines\n"
                        + "\n" + "SYNOPSIS\n" + "        ArgsMultiLineDescription [ -v ]\n" + "\n" + "OPTIONS\n"
                        + "        -v\n" + "            Verbose descriptions\n" + "            have new lines\n"
                        + "\n");
        //@formatter:on
    }

    public void testMultiParagraphDiscussion() throws IOException {
        SingleCommand<ArgsMultiParagraphDiscussion> cmd = singleCommand(ArgsMultiParagraphDiscussion.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        ArgsMultiParagraphDiscussion -\n" + "\n" + "SYNOPSIS\n"
                        + "        ArgsMultiParagraphDiscussion\n" + "\n" + "DISCUSSION\n"
                        + "        First paragraph\n" + "\n" + "        Middle paragraph\n" + "\n"
                        + "        Final paragraph\n" + "\n");
        //@formatter:on
    }

    public void testInheritedDiscussion() throws IOException {
        SingleCommand<ArgsInheritedDiscussion> cmd = singleCommand(ArgsInheritedDiscussion.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        ArgsInheritedDiscussion -\n" + "\n" + "SYNOPSIS\n"
                        + "        ArgsInheritedDiscussion\n" + "\n" + "DISCUSSION\n" + "        First paragraph\n"
                        + "\n" + "        Middle paragraph\n" + "\n" + "        Final paragraph\n" + "\n");
        //@formatter:on
    }

    public void testHiddenDiscussion() throws IOException {
        SingleCommand<ArgsHiddenDiscussion> cmd = singleCommand(ArgsHiddenDiscussion.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8), "NAME\n" + "        ArgsHiddenDiscussion -\n" + "\n"
                + "SYNOPSIS\n" + "        ArgsHiddenDiscussion\n" + "\n");
        //@formatter:on
    }

    public void testRestoredDiscussion() throws IOException {
        SingleCommand<ArgsRestoredDiscussion> cmd = singleCommand(ArgsRestoredDiscussion.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        ArgsRestoredDiscussion -\n" + "\n" + "SYNOPSIS\n"
                        + "        ArgsRestoredDiscussion\n" + "\n" + "DISCUSSION\n" + "        Restored\n" + "\n");
        //@formatter:on
    }

    public void testExamples() throws IOException {
        SingleCommand<ArgsExamples> cmd = singleCommand(ArgsExamples.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        ArgsExamples -\n" + "\n" + "SYNOPSIS\n" + "        ArgsExamples\n" + "\n"
                        + "EXAMPLES\n" + "        ArgsExample\n" + "\n" + "            Does nothing\n" + "\n"
                        + "        ArgsExample foo bar\n" + "\n" + "            Foos a bar\n" + "\n");
        //@formatter:on
    }

    public void testCopyrightLicense() throws IOException {
        SingleCommand<ArgsCopyrightAndLicense> cmd = singleCommand(ArgsCopyrightAndLicense.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(cmd.getCommandMetadata(), out);
        //@formatter:off
        testStringAssert(new String(out.toByteArray(), utf8), "NAME\n" + "        ArgsCopyrightAndLicense -\n"
                + "\n" + "SYNOPSIS\n" + "        ArgsCopyrightAndLicense\n" + "\n" + "COPYRIGHT\n"
                + "        Copyright (c) Acme Inc 2015-2016\n" + "\n" + "LICENSE\n"
                + "        This software is open source under the Apache License 2.0\n" + "\n"
                + "        Please see http://apache.org/licenses/LICENSE-2.0 for more information\n" + "\n");
        //@formatter:on
    }

    public void testGit() throws IOException {
        //@formatter:off
        CliBuilder<Runnable> builder = Cli.<Runnable>builder("git").withDescription("the stupid content tracker")
                .withDefaultCommand(Help.class).withCommand(Help.class).withCommand(Add.class);

        builder.withGroup("remote").withDescription("Manage set of tracked repositories")
                .withDefaultCommand(RemoteShow.class).withCommand(RemoteShow.class).withCommand(RemoteAdd.class);

        Cli<Runnable> gitParser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(gitParser.getMetadata(), Collections.<String>emptyList(), out);
        assertEquals(new String(out.toByteArray(), utf8), "usage: git [ -v ] <command> [ <args> ]\n" + "\n"
                + "Commands are:\n" + "    add      Add file contents to the index\n"
                + "    help     Display help information\n" + "    remote   Manage set of tracked repositories\n"
                + "\n" + "See 'git help <command>' for more information on a specific command.\n");

        out = new ByteArrayOutputStream();
        Help.help(gitParser.getMetadata(), AirlineUtils.singletonList("add"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        git add - Add file contents to the index\n" + "\n" + "SYNOPSIS\n"
                        + "        git [ -v ] add [ -i ] [--] [ <patterns>... ]\n" + "\n" + "OPTIONS\n"
                        + "        -i\n" + "            Add modified contents interactively.\n" + "\n"
                        + "        -v\n" + "            Verbose mode\n" + "\n" + "        --\n"
                        + "            This option can be used to separate command-line options from the\n"
                        + "            list of arguments (useful when arguments might be mistaken for\n"
                        + "            command-line options)\n" + "\n" + "        <patterns>\n"
                        + "            Patterns of files to be added\n" + "\n");

        out = new ByteArrayOutputStream();
        Help.help(gitParser.getMetadata(), AirlineUtils.singletonList("remote"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        git remote - Manage set of tracked repositories\n" + "\n" + "SYNOPSIS\n"
                        + "        git [ -v ] remote { add | show* } [--] [cmd-options] <cmd-args>\n" + "\n"
                        + "        Where command-specific options [cmd-options] are:\n"
                        + "            add: [ -t <branch> ]\n" + "            show: [ -n ]\n" + "\n"
                        + "        Where command-specific arguments <cmd-args> are:\n"
                        + "            add: [ <name> <url>... ]\n" + "            show: [ <remote> ]\n" + "\n"
                        + "        Where * indicates the default command(s)\n"
                        + "        See 'git help remote <command>' for more information on a specific command.\n"
                        + "OPTIONS\n" + "        -v\n" + "            Verbose mode\n" + "\n");

        out = new ByteArrayOutputStream();
        Help.help(gitParser.getMetadata(), AirlineUtils.arrayToList(new String[] { "remote", "add" }), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        git remote add - Adds a remote\n" + "\n" + "SYNOPSIS\n"
                        + "        git [ -v ] remote add [ -t <branch> ] [--] [ <name> <url>... ]\n" + "\n"
                        + "OPTIONS\n" + "        -t <branch>\n" + "            Track only a specific branch\n"
                        + "\n" + "        -v\n" + "            Verbose mode\n" + "\n" + "        --\n"
                        + "            This option can be used to separate command-line options from the\n"
                        + "            list of arguments (useful when arguments might be mistaken for\n"
                        + "            command-line options)\n" + "\n" + "        <name> <url>\n"
                        + "            Name and URL of remote repository to add\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgs1() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, Args1.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("Args1"), out);
        testStringAssert(new String(out.toByteArray(), utf8), "NAME\n" + "        test Args1 - args1 description\n"
                + "\n" + "SYNOPSIS\n" + "        test Args1 [ -bigdecimal <bigd> ] [ -date <date> ] [ -debug ]\n"
                + "                [ -double <doub> ] [ -float <floa> ] [ -groups <groups> ]\n"
                + "                [ {-log | -verbose} <verbose> ] [ -long <l> ] [--] [ <parameters>... ]\n" + "\n"
                + "OPTIONS\n" + "        -bigdecimal <bigd>\n" + "            A BigDecimal number\n" + "\n"
                + "        -date <date>\n" + "            An ISO 8601 formatted date.\n" + "\n" + "        -debug\n"
                + "            Debug mode\n" + "\n" + "        -double <doub>\n" + "            A double number\n"
                + "\n" + "        -float <floa>\n" + "            A float number\n" + "\n"
                + "        -groups <groups>\n" + "            Comma-separated list of group names to be run\n"
                + "\n" + "        -log <verbose>, -verbose <verbose>\n" + "            Level of verbosity\n" + "\n"
                + "        -long <l>\n" + "            A long number\n" + "\n" + "        --\n"
                + "            This option can be used to separate command-line options from the\n"
                + "            list of arguments (useful when arguments might be mistaken for\n"
                + "            command-line options)\n" + "\n" + "        <parameters>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgs2() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, Args2.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("Args2"), out);
        assertEquals(new String(out.toByteArray(), utf8), "NAME\n" + "        test Args2 -\n" + "\n" + "SYNOPSIS\n"
                + "        test Args2 [ -debug ] [ -groups <groups> ] [ -host <hosts>... ]\n"
                + "                [ {-log | -verbose} <verbose> ] [--] [ <parameters>... ]\n" + "\n" + "OPTIONS\n"
                + "        -debug\n" + "            Debug mode\n" + "\n" + "        -groups <groups>\n"
                + "            Comma-separated list of group names to be run\n" + "\n" + "        -host <hosts>\n"
                + "            The host\n" + "\n" + "        -log <verbose>, -verbose <verbose>\n"
                + "            Level of verbosity\n" + "\n" + "        --\n"
                + "            This option can be used to separate command-line options from the\n"
                + "            list of arguments (useful when arguments might be mistaken for\n"
                + "            command-line options)\n" + "\n" + "        <parameters>\n"
                + "            List of parameters\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgsAllowedValues() throws IOException {
        //@formatter:off
        SingleCommand<ArgsAllowedValues> command = singleCommand(ArgsAllowedValues.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage("test", null, command.getCommandMetadata().getName(),
                command.getCommandMetadata(), null, out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test ArgsAllowedValues - ArgsAllowedValues description\n" + "\n" + "SYNOPSIS\n"
                        + "        test ArgsAllowedValues [ -mode <mode> ]\n" + "\n" + "OPTIONS\n"
                        + "        -mode <mode>\n" + "            A string from a restricted set of values\n" + "\n"
                        + "            This options value is restricted to the following set of values:\n"
                        + "                a\n" + "                b\n" + "                c\n" + "\n");

        //@formatter:on
    }

    @Test
    public void testPartialRestriction() throws IOException {
        //@formatter:off
        SingleCommand<PartialAnnotated> command = singleCommand(PartialAnnotated.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage("test", null, command.getCommandMetadata().getName(),
                command.getCommandMetadata(), null, out);
        assertEquals(new String(out.toByteArray()),
                StringUtils.join(new String[] { "NAME", "        test partial -", "", "SYNOPSIS",
                        "        test partial [ --kvp <kvps>... ] [--] [ <args>... ]", "", "OPTIONS",
                        "        --kvp <kvps> <kvps>", "", "",
                        "            The following restriction only applies to the 1st value:",
                        "            This options value cannot be blank (empty or all whitespace)", "", "",
                        "        --",
                        "            This option can be used to separate command-line options from the",
                        "            list of arguments (useful when arguments might be mistaken for",
                        "            command-line options)", "", "        <args>", "", "", "" }, '\n'));
        //@formatter:on
    }

    @Test
    public void testArgsAritySting() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, ArgsArityString.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("ArgsArityString"), out);
        assertEquals(new String(out.toByteArray(), utf8), "NAME\n" + "        test ArgsArityString -\n" + "\n"
                + "SYNOPSIS\n" + "        test ArgsArityString [ -pairs <pairs>... ] [--] [ <rest>... ]\n" + "\n"
                + "OPTIONS\n" + "        -pairs <pairs> <pairs>\n" + "            Pairs\n" + "\n" + "        --\n"
                + "            This option can be used to separate command-line options from the\n"
                + "            list of arguments (useful when arguments might be mistaken for\n"
                + "            command-line options)\n" + "\n" + "        <rest>\n" + "            Rest\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgsBooleanArity() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, ArgsBooleanArity.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("ArgsBooleanArity"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test ArgsBooleanArity -\n" + "\n" + "SYNOPSIS\n"
                        + "        test ArgsBooleanArity [ -debug <debug> ]\n" + "\n" + "OPTIONS\n"
                        + "        -debug <debug>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgsInherited() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, ArgsInherited.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("ArgsInherited"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test ArgsInherited -\n" + "\n" + "SYNOPSIS\n"
                        + "        test ArgsInherited [ -child <child> ] [ -debug ] [ -groups <groups> ]\n"
                        + "                [ -level <level> ] [ -log <log> ] [--] [ <parameters>... ]\n" + "\n"
                        + "OPTIONS\n" + "        -child <child>\n" + "            Child parameter\n" + "\n"
                        + "        -debug\n" + "            Debug mode\n" + "\n" + "        -groups <groups>\n"
                        + "            Comma-separated list of group names to be run\n" + "\n"
                        + "        -level <level>\n" + "            A long number\n" + "\n" + "        -log <log>\n"
                        + "            Level of verbosity\n" + "\n" + "        --\n"
                        + "            This option can be used to separate command-line options from the\n"
                        + "            list of arguments (useful when arguments might be mistaken for\n"
                        + "            command-line options)\n" + "\n" + "        <parameters>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testArgsRequired() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, ArgsRequired.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("ArgsRequired"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test ArgsRequired -\n" + "\n" + "SYNOPSIS\n"
                        + "        test ArgsRequired [--] <parameters>...\n" + "\n" + "OPTIONS\n" + "        --\n"
                        + "            This option can be used to separate command-line options from the\n"
                        + "            list of arguments (useful when arguments might be mistaken for\n"
                        + "            command-line options)\n" + "\n" + "        <parameters>\n"
                        + "            List of files\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testOptionsRequired() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, OptionsRequired.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("OptionsRequired"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test OptionsRequired -\n" + "\n" + "SYNOPSIS\n"
                        + "        test OptionsRequired [ --optional <optionalOption> ]\n"
                        + "                --required <requiredOption>\n" + "\n" + "OPTIONS\n"
                        + "        --optional <optionalOption>\n" + "\n" + "\n"
                        + "        --required <requiredOption>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testOptionsHidden01() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, OptionsHidden.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("OptionsHidden"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test OptionsHidden -\n" + "\n" + "SYNOPSIS\n"
                        + "        test OptionsHidden [ --optional <optionalOption> ]\n" + "\n" + "OPTIONS\n"
                        + "        --optional <optionalOption>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testOptionsHidden02() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, OptionsHidden.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        AbstractCommandUsageGenerator generator = new CliCommandUsageGenerator(true);
        CommandMetadata metadata = CollectionUtils.find(parser.getMetadata().getDefaultGroupCommands(),
                new CommandFinder("OptionsHidden"));
        Assert.assertNotNull(metadata);
        generator.usage("test", null, "OptionsHidden", metadata, null, out);

        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test OptionsHidden -\n" + "\n" + "SYNOPSIS\n"
                        + "        test OptionsHidden [ --hidden <hiddenOption> ]\n"
                        + "                [ --optional <optionalOption> ]\n" + "\n" + "OPTIONS\n"
                        + "        --hidden <hiddenOption>\n" + "\n\n" + "        --optional <optionalOption>\n"
                        + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testGlobalOptionsHidden() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, GlobalOptionsHidden.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("GlobalOptionsHidden"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test GlobalOptionsHidden -\n" + "\n" + "SYNOPSIS\n"
                        + "        test [ {-op | --optional} ] GlobalOptionsHidden\n" + "\n" + "OPTIONS\n"
                        + "        -op, --optional\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testCommandHidden() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withDefaultCommand(Help.class).withCommands(Help.class, ArgsRequired.class, CommandHidden.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), Collections.<String>emptyList(), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "usage: test <command> [ <args> ]\n" + "\n" + "Commands are:\n" + "    ArgsRequired\n"
                        + "    help           Display help information\n" + "\n"
                        + "See 'test help <command>' for more information on a specific command.\n");

        out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("CommandHidden"), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test CommandHidden -\n" + "\n" + "SYNOPSIS\n"
                        + "        test CommandHidden [ --optional <optionalOption> ]\n" + "\n" + "OPTIONS\n"
                        + "        --optional <optionalOption>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testGroups() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withCommand(Help.class);
        builder.withGroup("visible").withDescription("Visible group").withCommands(ArgsRequired.class);

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), Collections.<String>emptyList(), out);
        Assert.assertEquals(new String(out.toByteArray(), utf8),
                "usage: test <command> [ <args> ]\n" + "\n" + "Commands are:\n"
                        + "    help      Display help information\n" + "    visible   Visible group\n" + "\n"
                        + "See 'test help <command>' for more information on a specific command.\n");
        //@formatter:on
    }

    @Test
    public void testGroupsHidden01() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withCommand(Help.class);
        builder.withGroup("visible").withDescription("Visible group").withCommands(ArgsRequired.class);
        builder.withGroup("hidden").withDescription("Hidden group").withCommands(ArgsRequired.class).makeHidden();

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), Collections.<String>emptyList(), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "usage: test <command> [ <args> ]\n" + "\n" + "Commands are:\n"
                        + "    help      Display help information\n" + "    visible   Visible group\n" + "\n"
                        + "See 'test help <command>' for more information on a specific command.\n");
        //@formatter:on
    }

    @Test
    public void testGroupsHidden02() throws IOException {
        //@formatter:off
        CliBuilder<Object> builder = Cli.builder("test").withDescription("Test commandline")
                .withCommand(Help.class);
        builder.withGroup("visible").withDescription("Visible group").withCommands(ArgsRequired.class);
        builder.withGroup("hidden").withDescription("Hidden group").withCommands(ArgsRequired.class).makeHidden();

        Cli<Object> parser = builder.build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        CliGlobalUsageSummaryGenerator<Object> generator = new CliGlobalUsageSummaryGenerator<Object>(true);
        generator.usage(parser.getMetadata(), out);
        assertEquals(new String(out.toByteArray(), utf8),
                "usage: test <command> [ <args> ]\n" + "\n" + "Commands are:\n"
                        + "    help      Display help information\n" + "    hidden    Hidden group\n"
                        + "    visible   Visible group\n" + "\n"
                        + "See 'test help <command>' for more information on a specific command.\n");
        //@formatter:on
    }

    @Test
    public void testExamplesAndDiscussion() throws IOException {
        //@formatter:off
        Cli<?> parser = Cli.builder("git").withCommand(CommandRemove.class).build();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Help.help(parser.getMetadata(), AirlineUtils.singletonList("remove"), out);

        String discussion = "DISCUSSION\n" + "        More details about how this removes files from the index.\n"
                + "\n";

        String examples = "EXAMPLES\n" + "        $ git remove -i myfile.java\n\n"
                + "            This is a usage example";

        String usage = new String(out.toByteArray(), utf8);
        System.out.println(usage);
        assertTrue(usage.contains(discussion), "Expected the discussion section to be present in the help");
        assertTrue(usage.contains(examples), "Expected the examples section to be present in the help");
        //@formatter:on
    }

    @Test
    public void testSingleCommandArgs1() throws IOException {
        //@formatter:off
        SingleCommand<Args1> command = singleCommand(Args1.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        assertEquals(new String(out.toByteArray(), utf8), "NAME\n" + "        test - args1 description\n" + "\n"
                + "SYNOPSIS\n" + "        test [ -bigdecimal <bigd> ] [ -date <date> ] [ -debug ]\n"
                + "                [ -double <doub> ] [ -float <floa> ] [ -groups <groups> ]\n"
                + "                [ {-log | -verbose} <verbose> ] [ -long <l> ] [--] [ <parameters>... ]\n" + "\n"
                + "OPTIONS\n" + "        -bigdecimal <bigd>\n" + "            A BigDecimal number\n" + "\n"
                + "        -date <date>\n" + "            An ISO 8601 formatted date.\n" + "\n" + "        -debug\n"
                + "            Debug mode\n" + "\n" + "        -double <doub>\n" + "            A double number\n"
                + "\n" + "        -float <floa>\n" + "            A float number\n" + "\n"
                + "        -groups <groups>\n" + "            Comma-separated list of group names to be run\n"
                + "\n" + "        -log <verbose>, -verbose <verbose>\n" + "            Level of verbosity\n" + "\n"
                + "        -long <l>\n" + "            A long number\n" + "\n" + "        --\n"
                + "            This option can be used to separate command-line options from the\n"
                + "            list of arguments (useful when arguments might be mistaken for\n"
                + "            command-line options)\n" + "\n" + "        <parameters>\n" + "\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testExitCodes() throws IOException {
        //@formatter:off
        SingleCommand<ArgsExitCodes> command = singleCommand(ArgsExitCodes.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test - ArgsExitCodes description\n" + "\n" + "SYNOPSIS\n" + "        test\n"
                        + "\n" + "EXIT CODES\n" + "        This command returns one of the following exit codes:\n"
                        + "\n" + "            0   Success\n" + "            1\n" + "            2   Error 2\n\n");
        //@formatter:on
    }

    @Test
    public void testVersion() throws IOException {
        //@formatter:off
        SingleCommand<ArgsVersion> command = singleCommand(ArgsVersion.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test - ArgsVersion description\n" + "\n" + "SYNOPSIS\n" + "        test\n"
                        + "\n" + "VERSION\n" + "            Component: Airline Test\n"
                        + "            Version: 1.2.3\n" + "            Build: 12345abcde\n");
        //@formatter:on
    }

    @Test
    public void testVersionComponents() throws IOException {
        //@formatter:off
        SingleCommand<ArgsVersion2> command = singleCommand(ArgsVersion2.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        testStringAssert(new String(out.toByteArray(), utf8),
                "NAME\n" + "        test - Multiple component versions\n" + "\n" + "SYNOPSIS\n" + "        test\n"
                        + "\n" + "VERSION\n" + "            Component: Airline Test\n"
                        + "            Version: 1.2.3\n" + "            Build: 12345abcde\n" + "\n"
                        + "            Component: Foo\n" + "            Build: 789\n"
                        + "            Build Date: Feb 2016\n" + "            Author: Mr Foo\n" + "\n"
                        + "            Component: Bar\n" + "            Version: 1.0.7\n"
                        + "            Built With: Oracle JDK 1.7\n" + "            Author: Mrs Bar\n");
        //@formatter:on
    }

    @Test
    public void testVersionComponentsTabular() throws IOException {
        //@formatter:off
        SingleCommand<ArgsVersion3> command = singleCommand(ArgsVersion3.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        testStringAssert(new String(out.toByteArray(), utf8), "NAME\n"
                + "        test - Multiple component versions\n" + "\n" + "SYNOPSIS\n" + "        test\n" + "\n"
                + "VERSION\n"
                + "            Component      Version   Build        Build Date   Author    Built With\n"
                + "            Airline Test   1.2.3     12345abcde\n"
                + "            Foo                      789          Feb 2016     Mr Foo\n"
                + "            Bar            1.0.7                               Mrs Bar   Oracle JDK 1.7\n\n");
        //@formatter:on
    }

    @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*missing\\.version.*")
    public void testVersionMissing() throws IOException {
        singleCommand(ArgsVersionMissing.class);
    }

    @Test
    public void testVersionMissingSupressed() throws IOException {
        //@formatter:off
        SingleCommand<ArgsVersionMissingSuppressed> command = singleCommand(ArgsVersionMissingSuppressed.class);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new CliCommandUsageGenerator().usage(null, null, "test", command.getCommandMetadata(), null, out);
        testStringAssert(new String(out.toByteArray(), utf8), "NAME\n"
                + "        test - Missing version information\n" + "\n" + "SYNOPSIS\n" + "        test\n" + "\n");
        //@formatter:on
    }

    @Test
    public void testCommandSorting() {
        //@formatter:off
        CliBuilder<Runnable> builder = Cli.<Runnable>builder("git").withDescription("the stupid content tracker")
                .withDefaultCommand(Help.class).withCommand(Help.class).withCommand(Add.class);

        builder.withGroup("remote").withDescription("Manage set of tracked repositories")
                .withDefaultCommand(RemoteShow.class).withCommand(RemoteShow.class).withCommand(RemoteAdd.class);

        Cli<Runnable> gitParser = builder.build();

        List<CommandMetadata> defCommands = new ArrayList<>(gitParser.getMetadata().getDefaultGroupCommands());
        Collections.sort(defCommands, UsageHelper.DEFAULT_COMMAND_COMPARATOR);

        Assert.assertEquals(defCommands.get(0).getName(), "add");
        Assert.assertEquals(defCommands.get(1).getName(), "help");

        // Check sort is stable
        Collections.sort(defCommands, UsageHelper.DEFAULT_COMMAND_COMPARATOR);

        Assert.assertEquals(defCommands.get(0).getName(), "add");
        Assert.assertEquals(defCommands.get(1).getName(), "help");

        //@formatter:on
    }
}