/* Self tests for GDB command definitions for GDB, the GNU debugger.

   Copyright (C) 2019-2020 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "cli/cli-cmds.h"
#include "cli/cli-decode.h"
#include "gdbsupport/selftest.h"

#include <map>

namespace selftests {

/* Verify some invariants of GDB commands documentation.  */

namespace help_doc_tests {

static unsigned int nr_failed_invariants;

/* Report a broken invariant and increments nr_failed_invariants.  */

static void
broken_doc_invariant (const char *prefix, const char *name, const char *msg)
{
  fprintf_filtered (gdb_stdout,
		    "help doc broken invariant: command '%s%s' help doc %s\n",
		    prefix, name, msg);
  nr_failed_invariants++;
}

/* Recursively walk the commandlist structures, and check doc invariants:
   - The first line of the doc must end with a '.'.
   - the doc must not end with a new line.
  If an invariant is not respected, produce a message and increment
  nr_failed_invariants.
  Note that we do not call SELF_CHECK in this function, as we want
  all commands to be checked before making the test fail.  */

static void
check_doc (struct cmd_list_element *commandlist, const char *prefix)
{
  struct cmd_list_element *c;

  /* Walk through the commands.  */
  for (c = commandlist; c; c = c->next)
    {
      /* Checks the doc has a first line terminated with a '.'.  */
      const char *p = c->doc;

      /* Position p on the first LF, or on terminating null byte.  */
      while (*p && *p != '\n')
	p++;
      if (p == c->doc)
	broken_doc_invariant
	  (prefix, c->name,
	   "is missing the first line terminated with a '.' character");
      else if (*(p-1) != '.')
	broken_doc_invariant
	  (prefix, c->name,
	   "first line is not terminated with a '.' character");

      /* Checks the doc is not terminated with a new line.  */
      if (c->doc[strlen (c->doc) - 1] == '\n')
	broken_doc_invariant
	  (prefix, c->name,
	   "has a superfluous trailing end of line");

      /* Check if this command has subcommands and is not an
	 abbreviation.  We skip checking subcommands of abbreviations
	 in order to avoid duplicates in the output.  */
      if (c->prefixlist != NULL && !c->abbrev_flag)
	{
	  /* Recursively call ourselves on the subcommand list,
	     passing the right prefix in.  */
	  check_doc (*c->prefixlist, c->prefixname);
	}
    }
}

static void
help_doc_invariants_tests ()
{
  nr_failed_invariants = 0;
  check_doc (cmdlist, "");
  SELF_CHECK (nr_failed_invariants == 0);
}

} /* namespace help_doc_tests */

/* Verify some invariants of GDB command structure.  */

namespace command_structure_tests {

/* Nr of commands in which a duplicated list is found.  */
unsigned int nr_duplicates = 0;
/* Nr of commands in a list having no valid prefix cmd.  */
unsigned int nr_invalid_prefixcmd = 0;

/* A map associating a list with the prefix leading to it.  */

std::map<cmd_list_element **, const char *> lists;

/* Store each command list in lists, associated with the prefix to reach it.  A
   list must only be found once.

   Verifies that all elements of the list have the same non-null prefix
   command.  */

static void
traverse_command_structure (struct cmd_list_element **list,
			    const char *prefix)
{
  struct cmd_list_element *c, *prefixcmd;

  auto dupl = lists.find (list);
  if (dupl != lists.end ())
    {
      fprintf_filtered (gdb_stdout,
			"list %p duplicated,"
			" reachable via prefix '%s' and '%s'."
			"  Duplicated list first command is '%s'\n",
			list,
			prefix, dupl->second,
			(*list)->name);
      nr_duplicates++;
      return;
    }

  lists.insert ({list, prefix});

  /* All commands of *list must have a prefix command equal to PREFIXCMD,
     the prefix command of the first command.  */
  if (*list == nullptr)
    prefixcmd = nullptr; /* A prefix command with an empty subcommand list.  */
  else
    prefixcmd = (*list)->prefix;

  /* Walk through the commands.  */
  for (c = *list; c; c = c->next)
    {
      /* If this command has subcommands and is not an alias,
	 traverse the subcommands.  */
      if (c->prefixlist != NULL && c->cmd_pointer == nullptr)
	{
	  /* Recursively call ourselves on the subcommand list,
	     passing the right prefix in.  */
	  traverse_command_structure (c->prefixlist, c->prefixname);
	}
      if (prefixcmd != c->prefix
	  || (prefixcmd == nullptr && *list != cmdlist))
	{
	  if (c->prefix == nullptr)
	    fprintf_filtered (gdb_stdout,
			      "list %p reachable via prefix '%s'."
			      "  command '%s' has null prefixcmd\n",
			      list,
			      prefix, c->name);
	  else
	    fprintf_filtered (gdb_stdout,
			      "list %p reachable via prefix '%s'."
			      "  command '%s' has a different prefixcmd\n",
			      list,
			      prefix, c->name);
	  nr_invalid_prefixcmd++;
	}
    }
}

/* Verify that a list of commands is present in the tree only once.  */

static void
command_structure_invariants_tests ()
{
  nr_duplicates = 0;
  nr_invalid_prefixcmd = 0;

  traverse_command_structure (&cmdlist, "");

  /* Release memory, be ready to be re-run.  */
  lists.clear ();

  SELF_CHECK (nr_duplicates == 0);
  SELF_CHECK (nr_invalid_prefixcmd == 0);
}

}

} /* namespace selftests */

void _initialize_command_def_selftests ();
void
_initialize_command_def_selftests ()
{
  selftests::register_test
    ("help_doc_invariants",
     selftests::help_doc_tests::help_doc_invariants_tests);

  selftests::register_test
    ("command_structure_invariants",
     selftests::command_structure_tests::command_structure_invariants_tests);
}
