#!/usr/bin/python

# scenario generator for reconf-inetd - reconfigure and restart inetd
# Copyright (C) 2011, 2012 Serafeim Zanikolas <sez@debian.org>
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

class ScenarioParams(object):
    def __init__(self, params):
        self.params = {}
        [self.add_item(key, description) for key, description in params.items()]
    def __iter__(self):
        return self.params.iterkeys()
    def describe(self, key):
        return self.params[key]
    def add_item(self, key, description):
        normalised_key = key.replace('an ', '').lstrip('a ').replace(' ', '_').upper()
        self.__setattr__(normalised_key, normalised_key)
        self.params[normalised_key] = description

def main(test_tag, test_service):
    conf_entry_status_values = ScenarioParams( dict([
        (s, 'Given an inetd.conf file with %s entry %s' % (s, test_service))
         for s in ['an enabled', 'a maintainer_disabled', 'a user_disabled', 'a missing'] ]))

    server_path_values = ScenarioParams( dict([
        (s, 'And a matching server file that %s' % s)
         for s in ['exists', 'does not exist'] ]))

    shadow_fragment_status_values = ScenarioParams( dict([
        (s, 'And a matching shadow fragment with %s server arguments for the %s inetd.conf entry' %
            (s, test_service)) for s in ['identical', 'different'] ]) )
    shadow_fragment_status_values.add_item('missing', 'And no matching shadow fragment')

    reconf_fragment_values = ScenarioParams( dict([
        (s, 'And %s reconf-inetd fragment for the service %s' % (s, test_service))
         for s in ['a matching', 'no matching'] ]))

    files = {}
    counters = {}
    SCENARIO_INDENTATION=4
    STEP_INDENTATION=SCENARIO_INDENTATION*2
    def get_or_open_file(feature):
        fd = files.get(feature)
        if fd is None:
            fname = '%s.feature' % (feature.replace(' ', '-'))
            fd = open(fname, 'w')
            files[feature] = fd
            counters[feature] = 1
            return (fd, 1)
        else:
            counters[feature] += 1
            return (fd, counters[feature])

    def writeline(line, indentation=STEP_INDENTATION):
        output_file.write('%s%s\n' % (' '*indentation, line))

    seen_scenarios = set()
    for conf_entry_status in conf_entry_status_values:
        for server_path in server_path_values:
            for reconf_fragment in reconf_fragment_values:
                for shadow_fragment_status in shadow_fragment_status_values:

                    # service addition scenarios
                    if server_path == server_path_values.EXISTS and \
                            conf_entry_status == conf_entry_status_values.MISSING and \
                            reconf_fragment == reconf_fragment_values.MATCHING:
                        feature = '%s service addition' % test_tag
                        action = ''.join([
                            'Then a new entry is added to inetd.conf ',
                            'for service %s\n' + ' '*STEP_INDENTATION,
                            'And a matching shadow fragment with ',
                            'identical server arguments for %s ',
                            'is created\n' + ' '*STEP_INDENTATION,
                            'And inetd is restarted']) % (test_service,
                                                          test_service)
                    # service removal scenarios
                    elif server_path == server_path_values.DOES_NOT_EXIST and \
                            conf_entry_status in (conf_entry_status_values.ENABLED,
                                                  conf_entry_status_values.MAINTAINER_DISABLED) and \
                            reconf_fragment == reconf_fragment_values.NO_MATCHING and \
                            shadow_fragment_status == shadow_fragment_status_values.IDENTICAL:
                        feature = '%s service removal' % test_tag
                        action = ''.join([
                            'Then the %s service entry is removed ',
                            'from inetd.conf\n' + ' '*STEP_INDENTATION,
                            'And the matching shadow fragment with ',
                            'identical server arguments for %s ',
                            'is removed\n' + ' '*STEP_INDENTATION,
                            'And inetd is restarted']) % (test_service,
                                                          test_service)
                    # service enable scenarios
                    elif server_path == server_path_values.EXISTS and \
                            conf_entry_status == conf_entry_status_values.MAINTAINER_DISABLED and \
                            reconf_fragment == reconf_fragment_values.MATCHING and \
                            shadow_fragment_status in (shadow_fragment_status_values.IDENTICAL,
                                                       shadow_fragment_status_values.DIFFERENT):
                        feature = '%s service enable' % test_tag
                        action = ''.join([
                            'Then the %s service entry in inetd.conf is '
                            'enabled\n' + ' '*STEP_INDENTATION,
                            'And inetd is restarted']) % test_service
                    # no action scenarios
                    else:
                        action = 'Then inetd.conf must remain unchanged'
                        feature = '%s no action' % test_tag

                    output_file, scenario_counter = get_or_open_file(feature)
                    scenario = '%s-%02d' % (feature, scenario_counter)

                    scenario_steps = [ conf_entry_status_values.describe(conf_entry_status),
                                       server_path_values.describe(server_path),
                                       reconf_fragment_values.describe(reconf_fragment) ]
                    if conf_entry_status != conf_entry_status_values.MISSING:
                        descr = shadow_fragment_status_values.describe(shadow_fragment_status)
                        scenario_steps.append(descr)
                    scenario_steps.extend(['When I run reconf-inetd',
                                           action + '\n'])

                    scenario_fingerprint = hash(''.join(scenario_steps))
                    if scenario_fingerprint in seen_scenarios:
                        print 'skipping already seen scenario:', scenario
                        continue

                    print 'writing %s scenario' % scenario
                    if scenario_counter == 1:
                        writeline('Feature: %s\n' % feature, indentation=0)
                    writeline('Scenario: %s' % scenario,
                              indentation=SCENARIO_INDENTATION)
                    [writeline(line) for line in scenario_steps]

                    seen_scenarios.add(scenario_fingerprint)

    [f.close() for f in files.values()]

if __name__ == '__main__':
    test_services = { 'plain' : 'ftpd_ssl,tcp,/usr/sbin/ftpd_ssl',
                      'tcpd'  : 'ftpd_ssl,tcp,/usr/sbin/tcpd /usr/sbin/ftpd_ssl' }

    for test_tag, test_service in test_services.items():
        main(test_tag, test_service)
