==================
Available Checkers
==================

path
----

This checker will copy the file or folder in the path specified into
the configuration folder. It will also create a listing file next to
the copied path containing a recursive listing of the copied file or
folder containing as much ownership and permission information is
available from the operating system on which Checker is running.

For example, the following configuration lines::

  path:/some/folder
  path:/another/file.cfg

.. -> checker_txt

results in the following files and folders being present in the
configuration folder:: 

  checker.txt
  another/file.cfg
  another/file.cfg.listing
  some/folder.listing
  some/folder/afile.cfg
  some/folder/bfile.cfg

.. -> expected

The listing files will contain information in the following style on
unix systems::

  /some/folder:
  drwxr-xr-x root root .
  -rw-r--r-- root root afile.cfg
  -rw-r--r-- root root bfile.cfg

.. -> expected_content

.. However, what was written likely looked more like:

  /some/folder:
  total 36
  drwxr-xr-x  2 root root 4096  .
  drwxr-xr-x 46 root root 4096  ..
  -rw-r--r--  1 root root 2425  afile.cfg
  -rw-r--r--  1 root root 1421  bfile.cfg
  
.. -> initial_content
  
.. invisible-code-block: python

  from __future__ import with_statement
  from checker.tests.base import CommandContext,listall
  from testfixtures import compare
  with CommandContext() as c:
    # pretend we're not on windows
    c.r.replace('subprocess.mswindows',False)
    # pretend the paths exist
    c.existing_paths.add('/some/folder')
    c.existing_paths.add('/another/file.cfg')
    # stub out the cp and ls calls
    c.add("cp -R '/some/folder' '<config>/some'",files=(
          ('some/folder/afile.cfg','content'),
          ('some/folder/bfile.cfg','content'),
          ))
    c.add("LC_COLLATE=\"C\" ls -laR --time-style=+ '/some/folder'",
          output=initial_content)
    c.add("cp -R '/another/file.cfg' '<config>/another'",files=(
          ('another/file.cfg','content'),
          ))
    c.add("LC_COLLATE=\"C\" ls -laR --time-style=+ '/another/file.cfg'",
          output=initial_content)
    # now run the config
    c.run_with_config(checker_txt)
    # check the calls
    compare(c.called,[
        "cp -R '/some/folder' '<config>/some'",
        "LC_COLLATE=\"C\" ls -laR --time-style=+ '/some/folder'",
        "cp -R '/another/file.cfg' '<config>/another'",
        "LC_COLLATE=\"C\" ls -laR --time-style=+ '/another/file.cfg'",
        'svn up -q <config>',
        'svn status <config>'
        ])
    # check the files are as expected
    compare(listall(c.dir,dir=False),expected.split())
    compare(c.dir.read(('some','folder.listing')),expected_content)

On Windows, they will look more like::


  Directory of C:\some\folder

  DOMAIN\User .
  DOMAIN\User afile.cfg
  DOMAIN\User bfile.cfg

.. -> expected_content

.. However, what was written likely looked more like:

   Volume in drive C has no label.
   Volume Serial Number is Something

   Directory of C:\some\folder

  11/11/2009  04:24    <DIR>          DOMAIN\User .
  11/11/2009  04:24    <DIR>          DOMAIN\User ..
  11/11/2009  04:19            39,546 DOMAIN\User afile.cfg
  11/11/2009  04:27            40,776 DOMAIN\User bfile.cfg
                 2 File(s)        242,933 bytes
  
       Total Files Listed:
                 2 File(s)        242,933 bytes
                 2 Dir(s)  16,412,004,352 bytes free
               
.. -> initial_content
  
.. invisible-code-block: python

  from __future__ import with_statement
  from checker.tests.base import CommandContext,listall
  from testfixtures import compare
  with CommandContext() as c:
    # pretend we are on windows
    c.r.replace('subprocess.mswindows',True)
    # fake file.cfg being filelike
    c.r.replace('os.path.isfile',
                lambda p:p.endswith('file.cfg'))
    # pretend the paths exist
    c.existing_paths.add('/some/folder')
    c.existing_paths.add('/another/file.cfg')
    # stub out the XCOPY and DIR calls
    c.add('xcopy /I /E /Q /H /R /Y "/some/folder" "<config>/some/folder"',files=(
          ('some/folder/afile.cfg','content'),
          ('some/folder/bfile.cfg','content'),
          ))
    c.add('dir /q /s "/some/folder"',
          output=initial_content)
    c.add('copy "/another/file.cfg" "<config>/another/file.cfg"',files=(
          ('another/file.cfg','content'),
          ))
    c.add('dir /q /s "/another/file.cfg"',
          output=initial_content)
    # now run the config
    c.run_with_config(checker_txt)
    # check the calls
    compare(c.called,[
        'xcopy /I /E /Q /H /R /Y "/some/folder" "<config>/some/folder"',
        'dir /q /s "/some/folder"',
        'copy "/another/file.cfg" "<config>/another/file.cfg"',
        'dir /q /s "/another/file.cfg"',
        'svn up -q <config>',
        'svn status <config>'
        ])
    # check the files are as expected
    compare(listall(c.dir,dir=False),expected.split())
    compare(c.dir.read(('some','folder.listing')),expected_content)

This means that the implicit checker run on the configuration folder
will show up any changes in the contents, ownership or permissions of
any file or folder specified in a `path` checker.

svn
---

This checker will perform an :command:`svn update -q` followed by an
:command:`svn status` in the path specified.
The output will be filtered to remove any information that doesn't
indicate a changed file or folder, such as subversion externals that
are present in the checkout being examined.

For example, the following configuration line::

  svn:/some/folder

.. -> checker_txt

Will result in the following commands being executed::

  svn up -q /some/folder
  svn status /some/folder

.. -> commands

.. output can look like this:

  X       something
  M       /some/folder/subfolder/something.conf

  Performing status on external item at 'something'

.. -> output

.. but we want it to look like:

  M       /some/folder/subfolder/something.conf

.. -> wanted_output

.. invisible-code-block: python

  from __future__ import with_statement
  from checker.tests.base import CommandContext
  from testfixtures import compare
  with CommandContext() as c:
    # set up the command
    c.add('svn status /some/folder',output)
    #  run the config
    c.run_with_config(checker_txt)
    # check output
    c.output.check(
      ('root','INFO',wanted_output[:-1]),
      )
    # check the calls
    compare(c.called,commands.strip().split('\n')+[
        'svn up -q <config>',
        'svn status <config>'
        ])

This means if there are any unknown files or files that have been
changed in the checkout but not committed, they will be included in
the output from checker.

.. note:: This checker requires the :command:`svn` subversion client
          to be installed.

buildout
--------

This checker will execute a :command:`buildout -o -q` in the specified
path. If the specified path is also a subversion checkout, it makes
sense to preceed the `buildout` line with a `svn` line.

For example, the following configuration line::

  svn:/some/folder
  buildout:/some/folder

.. -> checker_txt

Will result in the following commands being executed::

  svn up -q /some/folder
  svn status /some/folder
  chdir /some/folder
  bin/buildout -o -q

.. -> commands

.. invisible-code-block: python

  from __future__ import with_statement
  with CommandContext() as c:
    # set up the commands
    c.add('svn status /some/folder','svn output')
    c.add('chdir /some/folder')
    c.add('bin/buildout -o -q','buildout output')
    #  run the config
    c.run_with_config(checker_txt)
    # check output
    c.output.check(
      ('root','INFO','svn output'),
      ('root','INFO','buildout output'),
      )
    # check the calls
    compare(c.called,commands.strip().split('\n')+[
        'svn up -q <config>',
        'svn status <config>'
        ])

This will effectively keep your buildout up to date and make sure
that if any packages required by your buildout are not present, you
will be notified.

crontab
-------

This checker executes a :command:`crontab -l -u` for the user supplied
in the configuration and stores the output in a file in the
configuration folder.

For example, the following configuration line::

  crontab:root

.. -> checker_txt

Will result in the following being present in the configuration
folder::

  checker.txt
  crontabs/root

.. -> expected

The `root` file is likely to contain something like the following::

  # A comment
  * * * * * /your/cron/job
  0 0 * * * /your/other/cron job

.. -> expected_root

.. we expect the following command to be executed:

  crontab -l -u root

.. -> command

.. invisible-code-block: python

  from __future__ import with_statement
  with CommandContext() as c:
    # set up the commands
    c.add('crontab -l -u root',expected_root)
    #  run the config
    c.run_with_config(checker_txt)
    # check output
    c.output.check()
    # check the calls
    compare(c.called,[
        command.strip(),
        'svn up -q <config>',
        'svn status <config>'
        ])
    # check the files are as expected
    compare(listall(c.dir,dir=False),expected.split())
    # check the root file content
    compare(c.dir.read(('crontabs','root')),expected_root)

.. warning:: 

  For the `crontab` checker to work, Checker must be run as
  the `root` user.

dpkg
----

This checker executes a :command:`dpkg -l` and puts the output in file
called `dpkg` in the configuration folder.

For example, the following configuration line::

  dpkg:

.. -> checker_txt

Will result in the following files being present in the configuration
folder:: 

  checker.txt
  dpkg

.. -> expected

With :file:`dpkg` containing something like::

  Desired=Unknown/Install/Remove/Purge/Hold
  | Status=Not/Installed/Config-f/Unpacked/Failed-cfg/Half-inst/t-aWait/T-pend
  |/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)
  ||/ Name                      Version                   Description
  +++-=========================-=========================-==================================================================
  ii  ack-grep                  1.80-1                    A grep-like program specifically for large source trees
  ii  adduser                   3.105ubuntu1              add and remove users and groups
  ii  apache2                   2.2.8-1ubuntu0.10         Next generation, scalable, extendable web server
  
.. -> expected_content

.. we expect the following command to be executed:

  dpkg -l

.. -> command

.. invisible-code-block: python

  from __future__ import with_statement
  with CommandContext() as c:
    # set up the commands
    c.add('dpkg -l',expected_content)
    #  run the config
    c.run_with_config(checker_txt)
    # check output
    c.output.check()
    # check the calls
    compare(c.called,[
        command.strip(),
        'svn up -q <config>',
        'svn status <config>'
        ])
    # check the files are as expected
    compare(listall(c.dir),expected.split())
    # check the root file content
    compare(c.dir.read(('dpkg')),expected_content)

init
----

This checker records the run levels and priorities for the daemon
named in the parameter on Debian and Ubuntu systems.

It does this by executing :command:`update-rc.d -n -f remove` for the
daemon specified in the configuration and stores the output in a file
in the configuration folder.

For example, the following configuration line::

  init:mysql

.. -> checker_txt

Will result in the following being present in the configuration
folder::

  checker.txt
  init/mysql

.. -> expected

The `mysql` file is likely to contain something like the following::

   /etc/rc0.d/K21mysql
   /etc/rc1.d/K21mysql
   /etc/rc2.d/S19mysql
   /etc/rc3.d/S19mysql
   /etc/rc4.d/S19mysql
   /etc/rc5.d/S19mysql
   /etc/rc6.d/K21mysql

.. -> expected_content

.. However, what was written likely looked more like:

  Removing any system startup links for /etc/init.d/mysql ...
    /etc/rc0.d/K21mysql
    /etc/rc1.d/K21mysql
    /etc/rc2.d/S19mysql
    /etc/rc3.d/S19mysql
    /etc/rc4.d/S19mysql
    /etc/rc5.d/S19mysql
    /etc/rc6.d/K21mysql
               
.. -> initial_content

.. we expect the following command to be executed:

  /usr/sbin/update-rc.d -n -f mysql remove

.. -> command

.. invisible-code-block: python

  from __future__ import with_statement
  with CommandContext() as c:
    # set up the commands
    command = command.strip()
    c.add(command,initial_content[:-1])
    #  run the config
    c.run_with_config(checker_txt)
    # check output
    c.output.check()
    # check the calls
    compare(c.called,[
        command,
        'svn up -q <config>',
        'svn status <config>'
        ])
    # check the files are as expected
    compare(listall(c.dir,dir=False),expected.split())
    # check the mysql file content
    compare(c.dir.read(('init','mysql')),expected_content)
