#!/bin/env python

#######################################################################
# Copyright (C) 2016, 2018, 2019 David Palao
#
# This file is part of QULo.
#
#  QULo 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.
#
#  QULo 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 QULo.  If not, see <http://www.gnu.org/licenses/>.
#
#######################################################################

import unittest
from unittest.mock import patch, MagicMock, call, mock_open
from inspect import signature, Parameter

import qulo.conf
from qulo.sensors import sensor_factory

from qulo.constants import (
    QAGENT_TO_QMASTER_DATA_PORT_KEY, QAGENT_TO_QMASTER_DATA_PORT,
    QMASTER_HOST_KEY, QMASTER_HOST, QMASTER_PROGRAM,
    GRAPHITE_HOST, GRAPHITE_HOST_KEY, GRAPHITE_CARBON_RECEIVER_PICKLE_PORT,
    GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY, GRAPHITE_SECTION,
    OWN_LOG_FILE_KEY, DEFAULT_OWN_FILE_LOGGING_PATH,
    OWN_LOG_LEVEL_KEY, DEFAULT_OWN_FILE_LOGGER_LEVEL,
    OWN_LOG_MAXBYTES_KEY, OWN_LOG_BACKUPCOUNT_KEY,
    OWN_LOG_SECTION, 
)

class QuloDConfTestCase(unittest.TestCase):
    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def setUp(
            self, pset_argv, pget_cnf_frm_cmdl, pget_cnf_frm_cfgf, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        logger = MagicMock()
        self.logger = logger
        self.test_class = qulo.conf.QuloDConf
        self.empty_init_instance = self.test_class()
        mocked_cl_parser = MagicMock()
        self.empty_init_instance._cl_parser = mocked_cl_parser
        
    def test_instance_has_description_attribute(self):
        from qulo.constants import QULOD_DESCRIPTION
        self.assertEqual(self.empty_init_instance.description, QULOD_DESCRIPTION)

    def test_description_is_strable(self):
        str(self.empty_init_instance)

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_setup_logging_called_before_post_process_configuration(
            self, pset_argv, pget_cnf_frm_cmdl, pget_cnf_frm_cfgf, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        pprepare_logging.side_effect = Exception
        with self.assertRaises(Exception):
            self.test_class()
        ppost_process_configuration.assert_not_called()

    def test_instance_has_correct_default_values(self):
        from qulo.conf import QULOD_DEFAULT_PIDFILE
        from qulo.constants import QULOD_DEFAULT_CONFIGFILE
        from qulo.constants import PIDFILE_STR, ACTION_STR, CONFIGFILE_STR
        self.assertEqual(
            self.empty_init_instance.default_values[PIDFILE_STR], QULOD_DEFAULT_PIDFILE
        )
        self.assertEqual(
            self.empty_init_instance.default_values[CONFIGFILE_STR], QULOD_DEFAULT_CONFIGFILE
        )
        with self.assertRaises(KeyError):
            self.empty_init_instance.default_values[ACTION_STR]

    def test_init_parameters(self):
        s = signature(self.test_class)
        parameters = s.parameters
        self.assertEqual(len(parameters), 1)

    def test_init_parameter_args(self):
        s = signature(self.test_class)
        parameters = s.parameters
        param_args = [param.kind for param in parameters.values()][0]
        expected = Parameter.POSITIONAL_OR_KEYWORD
        self.assertEqual(expected, param_args)

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_calls_set_argv(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile,
            pset_config_file, pprepare_logging, ppost_process_configuration):
        args = MagicMock()
        self.test_class(args)
        pset_argv.assert_called_once_with(args)

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_without_arguments_calls_set_argv_properly(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile, 
            pset_config_file, pprepare_logging, ppost_process_configuration):
        self.test_class()
        pset_argv.assert_called_once_with(None)

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_calls_get_conf_from_command_line_after_set_argv(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile,
            pset_config_file, pprepare_logging, ppost_process_configuration):
        pget_conf_from_cmdl.side_effect = Exception
        with self.assertRaises(Exception):
            self.test_class("jama")
        pget_conf_from_cmdl.assert_called_once_with()
        pset_argv.assert_called_once_with("jama")

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_set_config_file(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        self.test_class()
        pset_config_file.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_calls_get_conf_from_config_file_after_set_config_file(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        pget_conf_from_configfile.side_effect = Exception
        with self.assertRaises(Exception):
            self.test_class()
        pget_conf_from_configfile.assert_called_once_with()
        pset_config_file.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_calls_prepare_logging_after_get_conf_from_configfile(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        pprepare_logging.side_effect = Exception
        with self.assertRaises(Exception):
            self.test_class()
        pprepare_logging.assert_called_once_with()
        pget_conf_from_configfile.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QuloDConf._prepare_logging")
    @patch("qulo.conf.QuloDConf._set_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_config_file")
    @patch("qulo.conf.QuloDConf._get_conf_from_command_line")
    @patch("qulo.conf.QuloDConf._set_argv")
    def test_init_calls_post_process_configuration_after_prepare_logging(
            self, pset_argv, pget_conf_from_cmdl, pget_conf_from_configfile, pset_config_file,
            pprepare_logging, ppost_process_configuration):
        ppost_process_configuration.side_effect = Exception
        with self.assertRaises(Exception):
            self.test_class()
        ppost_process_configuration.assert_called_once_with()
        pprepare_logging.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._parse_config_file")
    @patch("qulo.conf.QuloDConf._create_config_parser")
    def test_get_conf_from_config_file_call_sequence(
            self, pcreate_config_parser, pparse_config_file):
        manager = MagicMock()
        manager.attach_mock(pcreate_config_parser, "_create_config_parser")
        manager.attach_mock(pparse_config_file, "_parse_config_file")
        expected_calls = [
            call._create_config_parser(),
            call._parse_config_file(),
        ]
        instance = self.empty_init_instance
        instance._config_file = MagicMock()
        instance._config_file_conf = MagicMock()
        instance._get_conf_from_config_file()
        manager.assert_has_calls(expected_calls)

    @patch("qulo.conf.QuloDConf._logging_from_config_file")
    @patch("qulo.conf.setup_logging")
    def test_prepare_logging_sets_logger_attribute(self, psetup_logging, plogging_from_config_file):
        logger = MagicMock()
        psetup_logging.return_value = logger
        self.empty_init_instance.logging = MagicMock()
        with self.assertRaises(AttributeError):
            self.empty_init_instance.logger
        self.empty_init_instance._prepare_logging()
        self.empty_init_instance.logger

    @patch("qulo.conf.QuloDConf._logging_from_config_file")
    @patch("qulo.conf.setup_logging")
    def test_prepare_logging_calls_setup_logging_after_logging_from_config_file(
            self, psetup_logging, plogging_from_config_file):
        psetup_logging.side_effect = Exception
        self.empty_init_instance.logging = MagicMock()
        with self.assertRaises(Exception):
            self.empty_init_instance._prepare_logging()
        psetup_logging.assert_called_once_with(
            logger_name=self.empty_init_instance.__class__.__name__,
            rotatingfile_conf=self.empty_init_instance.logging
        )
        plogging_from_config_file.assert_called_once_with()

    @patch("qulo.conf.configparser.ConfigParser")
    def test_create_config_parser_sets_config_file_conf_attribute(self, pconfigparser):
        instance = self.empty_init_instance
        conf = MagicMock()
        pconfigparser.return_value = conf
        instance._create_config_parser()
        self.assertEqual(instance._config_file_conf, conf)

    def test_parse_config_file_calls_configparser_read_method(self):
        from qulo.constants import CONFIGFILE_STR
        instance = self.empty_init_instance
        conf = MagicMock()
        configfile = MagicMock()
        instance._config_file_conf = conf
        instance._config_file = configfile
        instance._parse_config_file()
        conf.read.assert_called_once_with(configfile)

    def test_set_argv(self):
        args = MagicMock()
        self.empty_init_instance._set_argv(args)
        self.assertEqual(self.empty_init_instance._argv, args)

    @patch("qulo.conf.sys.argv")
    def test_set_argv_with_None(self, sys_argv):
        args = None
        self.empty_init_instance._set_argv(args)
        self.assertEqual(self.empty_init_instance._argv, sys_argv[1:])

    @patch("qulo.conf.QuloDConf.__getitem__")
    def test_set_config_file_sets_config_file_attribute(self, pgetitem):
        from qulo.constants import CONFIGFILE_STR
        instance = self.empty_init_instance
        mock_conf = MagicMock()
        def getitem(item):
            if item == CONFIGFILE_STR:
                return mock_conf
        pgetitem.side_effect = getitem
        instance._set_config_file()
        self.assertEqual(instance._config_file, mock_conf)

    @patch("qulo.conf.QuloDConf._parse_arguments")
    @patch("qulo.conf.QuloDConf._add_arguments")
    @patch("qulo.conf.QuloDConf._create_cl_parser")
    def test_get_conf_from_command_line_calls_create_cl_parser_first_off(
            self, pcreate_cl_parser, padd_arguments, pparse_arguments):
        pcreate_cl_parser.side_effect = Exception
        with self.assertRaises(Exception):
            self.empty_init_instance._get_conf_from_command_line()
        pcreate_cl_parser.assert_called_once_with()
        padd_arguments.assert_not_called()
        pparse_arguments.assert_not_called()

    @patch("qulo.conf.QuloDConf._parse_arguments")
    @patch("qulo.conf.QuloDConf._add_arguments")
    @patch("qulo.conf.QuloDConf._create_cl_parser")
    def test_get_conf_from_command_line_calls_add_arguments_in_second_place(
            self, pcreate_cl_parser, padd_arguments, pparse_arguments):
        padd_arguments.side_effect = Exception
        with self.assertRaises(Exception):
            self.empty_init_instance._get_conf_from_command_line()
        pcreate_cl_parser.assert_called_once_with()
        padd_arguments.assert_called_once_with()
        pparse_arguments.assert_not_called()

    @patch("qulo.conf.QuloDConf._parse_arguments")
    @patch("qulo.conf.QuloDConf._add_arguments")
    @patch("qulo.conf.QuloDConf._create_cl_parser")
    def test_get_conf_from_command_line_calls_parse_arguments_last(
            self, pcreate_cl_parser, padd_arguments, pparse_arguments):
        self.empty_init_instance._get_conf_from_command_line()
        pcreate_cl_parser.assert_called_once_with()
        padd_arguments.assert_called_once_with()
        pparse_arguments.assert_called_once_with()

    @patch("qulo.conf.argparse.ArgumentParser")
    def test_create_cl_parser_creates_ArgumentParser_instance(self, pArgumentParser):
        self.empty_init_instance._create_cl_parser()
        pArgumentParser.assert_called_once_with(description=self.empty_init_instance.description)
        
    @patch("qulo.conf.argparse.ArgumentParser")
    def test_create_cl_parser_sets_cl_parser(self, pArgumentParser):
        parser = MagicMock()
        pArgumentParser.return_value = parser
        self.empty_init_instance._create_cl_parser()
        self.assertEqual(self.empty_init_instance._cl_parser, parser)

    def test_add_arguments_add_expected_arguments_to_cl_parser(self):
        from qulo.conf import (
            ACTION_ARGUMENT, PIDFILE_ARGUMENT, QULOD_DEFAULT_PIDFILE,
            CONFIGFILE_ARGUMENT, QULOD_DEFAULT_CONFIGFILE, 
        )
        conf = self.empty_init_instance
        conf._add_arguments()
        PIDFILE_ARGUMENT[1][1]["default"] = QULOD_DEFAULT_PIDFILE
        CONFIGFILE_ARGUMENT[1][1]["default"] = QULOD_DEFAULT_CONFIGFILE
        calls = []
        for name, args in ACTION_ARGUMENT, PIDFILE_ARGUMENT, CONFIGFILE_ARGUMENT:
            calls.append(call(*args[0], **args[1]))
        conf._cl_parser.add_argument.assert_has_calls(calls, any_order=True)

    def test_parse_arguments_calls_cl_parsers_parse_args_method(self):
        argv = MagicMock()
        conf = self.empty_init_instance
        conf._argv = argv
        conf._parse_arguments()
        conf._cl_parser.parse_args.assert_called_once_with(argv)

    def test_parse_arguments_sets_command_line_conf_attribute(self):
        args = MagicMock()
        conf = self.empty_init_instance
        conf._cl_parser.parse_args.return_value = args
        conf._argv = MagicMock()
        conf._parse_arguments()
        self.assertEqual(conf._command_line_conf, vars(args))

    def test_getitem_wraps_command_line_confs_getitem(self):
        key = MagicMock()
        confini = MagicMock()
        conf = self.empty_init_instance
        conf._command_line_conf = confini
        res = conf[key]
        self.assertEqual(res, conf._command_line_conf[key])

    @patch("qulo.conf.QuloDConf._qmaster_from_config_file")
    def test_post_process_configuration_calls_qmaster_from_config_file(
            self, pqmaster_from_config_file):
        self.empty_init_instance.logger = MagicMock()
        self.empty_init_instance._config_file = MagicMock()
        self.empty_init_instance._post_process_configuration()
        pqmaster_from_config_file.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._qmaster_from_config_file")
    def test_post_process_configuration_messages_to_logger(
            self, pqmaster_from_config_file):
        from qulo.constants import CONF_READ_MSG
        from qulo.logs import setup_logging
        instance = self.empty_init_instance
        logger = setup_logging(
            logger_name=instance.__class__.__name__,
            rotatingfile_conf={"filename": "/dev/null"},
        )
        instance.logger = logger
        config_file = MagicMock()
        instance._config_file = config_file
        msg = CONF_READ_MSG.format(config_file=config_file)
        other_msg = 'winnipeg\ngonzalez'
        with self.assertLogs(instance.__class__.__name__, level="INFO") as log:
            with patch('qulo.conf.open', mock_open(read_data=other_msg)) as m:
                instance._post_process_configuration()
        self.assertIn(msg, log.output[0])
        self.assertEqual(len(log.output), 3)
        for ielement, element in enumerate(other_msg.split("\n")):
            self.assertIn(f" ... {element}", log.output[ielement+1])
        m.assert_called_once_with(config_file)

    @patch("qulo.conf.QuloDConf._qmaster_from_config_file")
    def test_post_process_configuration_messages_to_logger_if_missing_conf_file(
            self, pqmaster_from_config_file):
        from qulo.constants import CONF_READ_MSG
        from qulo.logs import setup_logging
        instance = self.empty_init_instance
        logger = setup_logging(
            logger_name=instance.__class__.__name__,
            rotatingfile_conf={"filename": "/dev/null"},
        )
        instance.logger = logger
        config_file = MagicMock()
        mopen = MagicMock(side_effect=OSError)
        instance._config_file = config_file
        msg = CONF_READ_MSG.format(config_file=config_file)
        with self.assertLogs(instance.__class__.__name__, level="INFO") as log:
            with patch('qulo.conf.open', mopen) as m:
                instance._post_process_configuration()
        self.assertIn(msg, log.output[0])
        self.assertEqual(len(log.output), 1)
        m.assert_called_once_with(config_file)

    def test_qmaster_from_config_file_sets_qmaster_attribute(self):
        with self.assertRaises(AttributeError):
            self.empty_init_instance.qmaster
        self.empty_init_instance._config_file_conf = MagicMock()
        self.empty_init_instance._qmaster_from_config_file()
        self.empty_init_instance.qmaster
        
    def test_qmaster_from_config_file_returns_defaults_if_missing_section(self):
        self.empty_init_instance._config_file_conf = {}
        #self.empty_init_instance._config_file_conf.sections().return_value = None
        self.empty_init_instance._qmaster_from_config_file()
        qmaster = self.empty_init_instance.qmaster
        self.assertEqual(qmaster[QMASTER_HOST_KEY], QMASTER_HOST)
        self.assertEqual(qmaster[QAGENT_TO_QMASTER_DATA_PORT_KEY], QAGENT_TO_QMASTER_DATA_PORT)

    def test_qmaster_from_config_file_give_priority_to_file_over_defaults_in_qmaster_section(self):
        test_conf = {
            QMASTER_HOST_KEY: "winnipoj",
            QAGENT_TO_QMASTER_DATA_PORT_KEY: 23452
        }
        self.empty_init_instance._config_file_conf = {QMASTER_PROGRAM: test_conf}
        self.empty_init_instance._qmaster_from_config_file()
        qmaster = self.empty_init_instance.qmaster
        self.assertEqual(qmaster[QMASTER_HOST_KEY], "winnipoj")
        self.assertEqual(qmaster[QAGENT_TO_QMASTER_DATA_PORT_KEY], 23452)

    def test_qmaster_from_config_file_host_has_priority_over_default_in_qmaster_section(self):
        test_conf = {QMASTER_HOST_KEY: "winnipeg"}
        self.empty_init_instance._config_file_conf = {QMASTER_PROGRAM: test_conf}
        self.empty_init_instance._qmaster_from_config_file()
        qmaster = self.empty_init_instance.qmaster
        self.assertEqual(qmaster[QMASTER_HOST_KEY], "winnipeg")
        self.assertEqual(qmaster[QAGENT_TO_QMASTER_DATA_PORT_KEY], QAGENT_TO_QMASTER_DATA_PORT)

    def test_qmaster_from_config_file_port_has_priority_over_default_in_qmaster_section(self):
        test_conf = {QAGENT_TO_QMASTER_DATA_PORT_KEY: 54545}
        self.empty_init_instance._config_file_conf = {QMASTER_PROGRAM: test_conf}
        self.empty_init_instance._qmaster_from_config_file()
        qmaster = self.empty_init_instance.qmaster
        self.assertEqual(qmaster[QMASTER_HOST_KEY], QMASTER_HOST)
        self.assertEqual(qmaster[QAGENT_TO_QMASTER_DATA_PORT_KEY], 54545)

    def test_logging_from_config_file_sets_logging_attribute(self):
        with self.assertRaises(AttributeError):
            self.empty_init_instance.logging
        self.empty_init_instance._config_file_conf = MagicMock()
        self.empty_init_instance._logging_from_config_file()
        self.empty_init_instance.logging

    def test_logging_from_config_file_returns_defaults_if_missing_section(self):
        self.empty_init_instance._config_file_conf = {}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        self.assertEqual(logging[OWN_LOG_FILE_KEY], DEFAULT_OWN_FILE_LOGGING_PATH)
        self.assertEqual(logging[OWN_LOG_LEVEL_KEY], DEFAULT_OWN_FILE_LOGGER_LEVEL)

    def test_logging_from_config_file_give_priority_to_file_over_defaults_in_logging_section(self):
        test_conf = {
            OWN_LOG_FILE_KEY: "/winnipoj",
            OWN_LOG_LEVEL_KEY: "WHATEVER",
        }
        self.empty_init_instance._config_file_conf = {OWN_LOG_SECTION: test_conf}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        self.assertEqual(logging[OWN_LOG_FILE_KEY], "/winnipoj")
        self.assertEqual(logging[OWN_LOG_LEVEL_KEY], "WHATEVER")

    def test_logging_from_config_file_valid_optional_keys_if_given(self):
        test_conf = {
            OWN_LOG_MAXBYTES_KEY: "1005",
            OWN_LOG_BACKUPCOUNT_KEY: "4",
            "parida": 23,
        }
        self.empty_init_instance._config_file_conf = {OWN_LOG_SECTION: test_conf}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        self.assertEqual(logging[OWN_LOG_MAXBYTES_KEY], 1005)
        self.assertEqual(logging[OWN_LOG_BACKUPCOUNT_KEY], 4)
        with self.assertRaises(KeyError):
            logging["parida"]

    def test_logging_from_config_file_log_file_has_priority_over_default_in_logging_section(self):
        test_conf = {OWN_LOG_FILE_KEY: "/machu/pichu.log"}
        self.empty_init_instance._config_file_conf = {OWN_LOG_SECTION: test_conf}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        self.assertEqual(logging[OWN_LOG_FILE_KEY], "/machu/pichu.log")
        self.assertEqual(logging[OWN_LOG_LEVEL_KEY], DEFAULT_OWN_FILE_LOGGER_LEVEL)

    def test_logging_from_config_file_level_has_priority_over_default_in_logging_section(self):
        test_conf = {OWN_LOG_LEVEL_KEY: "CRITICAL"}
        self.empty_init_instance._config_file_conf = {OWN_LOG_SECTION: test_conf}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        self.assertEqual(logging[OWN_LOG_FILE_KEY], DEFAULT_OWN_FILE_LOGGING_PATH)
        self.assertEqual(logging[OWN_LOG_LEVEL_KEY], "CRITICAL")

    def test_logging_from_config_file_optional_values_raise_KeyError_if_not_given(self):
        test_conf = {}
        self.empty_init_instance._config_file_conf = {OWN_LOG_SECTION: test_conf}
        self.empty_init_instance._logging_from_config_file()
        logging = self.empty_init_instance.logging
        with self.assertRaises(KeyError):
            logging[OWN_LOG_MAXBYTES_KEY]
        with self.assertRaises(KeyError):
            logging[OWN_LOG_BACKUPCOUNT_KEY]
        
        
class QAgentConfTestCaseBase(unittest.TestCase):
    @patch("qulo.conf.QAgentConf._post_process_configuration")
    @patch("qulo.conf.QAgentConf._prepare_logging")
    @patch("qulo.conf.QAgentConf._set_config_file")                
    @patch("qulo.conf.QAgentConf._get_conf_from_config_file")
    @patch("qulo.conf.QAgentConf._get_conf_from_command_line")
    @patch("qulo.conf.QAgentConf._set_argv")
    def setUp(self, pset_argv, pget_cnf_frm_cmdl, pget_cnf_frm_cfgf, pset_config_file,
              psetup_logging, pfinalize_after_config_file):
        self.test_class = qulo.conf.QAgentConf
        self.empty_init_instance = self.test_class()
        mocked_cl_parser = MagicMock()
        self.empty_init_instance._cl_parser = mocked_cl_parser
        
    
class QAgentConfTestCase(QAgentConfTestCaseBase):
    def test_instance_has_description_attribute(self):
        from qulo.constants import QAGENT_DESCRIPTION
        self.assertEqual(self.empty_init_instance.description, QAGENT_DESCRIPTION)

    def test_is_subclass_of_QuloDConf(self):
        from qulo.conf import QuloDConf
        self.assertTrue(issubclass(self.test_class, QuloDConf))
        
    def test_description_is_strable(self):
        str(self.empty_init_instance)
        
    def test_instance_has_correct_default_values(self):
        from qulo.conf import QAGENT_DEFAULT_PIDFILE
        from qulo.constants import (
            PIDFILE_STR, ACTION_STR, CONFIGFILE_STR, QAGENT_DEFAULT_CONFIGFILE,
        )
        self.assertEqual(
            self.empty_init_instance.default_values[PIDFILE_STR], QAGENT_DEFAULT_PIDFILE)
        self.assertEqual(
            self.empty_init_instance.default_values[CONFIGFILE_STR], QAGENT_DEFAULT_CONFIGFILE
        )
        with self.assertRaises(KeyError):
            self.empty_init_instance.default_values[ACTION_STR]

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QAgentConf._sensors_from_config_file_sections")
    def test_post_process_configuration_calls_sensors_from_config_file_sections(
            self, psensors_from_config_file_sections, ppost_process_configuration):
        self.empty_init_instance._post_process_configuration()
        psensors_from_config_file_sections.assert_called_once_with()

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QAgentConf._sensors_from_config_file_sections")
    def test_post_process_configuration_calls_same_method_in_QuloDConf(
            self, psensors_from_config_file_sections,
            ppost_process_configuration):
        self.empty_init_instance._post_process_configuration()
        ppost_process_configuration.assert_called_once_with()

    def test_make_sensor_from_section_return_value_if_input_not_a_sensor(self):
        not_a_sensor = MagicMock()
        result = self.empty_init_instance._make_sensor_from_section(not_a_sensor)
        self.assertEqual(result, None)

    @patch("qulo.conf.sensor_factory")
    def test_make_sensor_from_section_return_value_if_input_is_a_sensor(self, psensor_factory):
        sensor = MagicMock()
        psensor_factory.return_value = sensor
        section = MagicMock()
        sensor_name = "{}".format(sensor)
        section.partition.return_value = ("sensor", ":", sensor_name)
        conf = MagicMock()
        conf_item = MagicMock()
        def getitem(item):
            if item == section:
                return conf_item
            else:
                return MagicMock()
        conf.__getitem__.side_effect = getitem
        self.empty_init_instance._config_file_conf = conf
        logging = MagicMock()
        self.empty_init_instance.logging = logging
        result = self.empty_init_instance._make_sensor_from_section(section)
        psensor_factory.assert_called_once_with(sensor_name, conf_item, logging)
        self.assertEqual(result, sensor)        

    @patch("qulo.conf.sensor_factory")
    def test_make_sensor_from_section_behaviour_if_NameError_raised(self, psensor_factory):
        from qulo.constants import PROTO_INVALID_SENSOR_MSG
        from qulo.logs import setup_logging
        psensor_factory.side_effect = NameError()
        section = MagicMock()
        sensor = MagicMock()
        section.partition.return_value = ("sensor", ":", sensor)
        self.empty_init_instance._config_file_conf = MagicMock()
        logging = MagicMock()
        self.empty_init_instance.logging = logging
        logger = setup_logging(
            logger_name=self.empty_init_instance.__class__.__name__,
            rotatingfile_conf={"filename": "/dev/null"},
        )
        self.empty_init_instance.logger = logger
        with self.assertLogs(self.empty_init_instance.__class__.__name__, "ERROR") as cm:
            result = self.empty_init_instance._make_sensor_from_section(section)
        self.assertEqual(result, None)
        self.assertIn(PROTO_INVALID_SENSOR_MSG.format(sensor_name=sensor), cm.output[0])

    
class QAgentConf_sensors_from_config_file_sectionsTestCase(QAgentConfTestCaseBase):
    def setUp(self):
        super().setUp()
        self.conf = MagicMock()
        self.empty_init_instance._config_file_conf = self.conf
        
    @patch("qulo.conf.QAgentConf._make_sensor_from_section")
    def test_sets_sensors_attribute(self, pmake_sensor_from_section):
        self.empty_init_instance._sensors_from_config_file_sections()
        self.assertTrue(hasattr(self.empty_init_instance, "sensors"))

    @patch("qulo.conf.QAgentConf._make_sensor_from_section")
    def test_iterates_over_sections(self, pmake_sensor_from_section):
        sections = MagicMock()
        self.conf.sections.return_value = sections
        self.empty_init_instance._sensors_from_config_file_sections()
        sections.__iter__.assert_called_once_with()

    @patch("qulo.conf.QAgentConf._make_sensor_from_section")
    def test_calls_make_sensor_from_section_with_each_section(self, pmake_sensor_from_section):
        sections = MagicMock()
        sections_results = [MagicMock() for _ in range(5)]
        sections.__iter__.return_value = sections_results
        self.conf.sections.return_value = sections
        expected_calls = [call(section) for section in sections_results]
        self.empty_init_instance._sensors_from_config_file_sections()
        pmake_sensor_from_section.assert_has_calls(expected_calls, any_order=True)

    @patch("qulo.conf.QAgentConf._make_sensor_from_section")
    def test_sensors_attribute_contents(self, pmake_sensor_from_section):
        sections = MagicMock()
        not_sensors = [MagicMock() for _ in range(5)]
        sensors = [MagicMock() for _ in range(5)]
        def my_side_effect(section):
            if section in sensors:
                return section
            else:
                return None
        in_sections = set(sensors+not_sensors)
        sections_results = list(in_sections)
        sections.__iter__.return_value = sections_results
        self.conf.sections.return_value = sections
        pmake_sensor_from_section.side_effect = my_side_effect
        self.empty_init_instance._sensors_from_config_file_sections()
        self.assertSetEqual(set(self.empty_init_instance.sensors), set(sensors))

    
class QMasterConfTestCase(unittest.TestCase):
    @patch("qulo.conf.QMasterConf._post_process_configuration")
    @patch("qulo.conf.QMasterConf._prepare_logging")
    @patch("qulo.conf.QMasterConf._set_config_file")                
    @patch("qulo.conf.QMasterConf._get_conf_from_config_file")
    @patch("qulo.conf.QMasterConf._get_conf_from_command_line")
    @patch("qulo.conf.QMasterConf._set_argv")
    def setUp(self, pset_argv, pget_cnf_frm_cmdl, pget_cnf_frm_cfgf, pset_config_file,
              pprepare_logging, ppost_process_configuration):
        self.test_class = qulo.conf.QMasterConf
        self.empty_init_instance = self.test_class()
        mocked_cl_parser = MagicMock()
        self.empty_init_instance._cl_parser = mocked_cl_parser

    def test_instance_has_description_attribute(self):
        from qulo.constants import QMASTER_DESCRIPTION
        self.assertEqual(self.empty_init_instance.description, QMASTER_DESCRIPTION)

    def test_is_subclass_of_QuloDConf(self):
        from qulo.conf import QuloDConf
        self.assertTrue(issubclass(self.test_class, QuloDConf))
        
    def test_description_is_strable(self):
        str(self.empty_init_instance)
        
    def test_instance_has_correct_default_values(self):
        from qulo.conf import QMASTER_DEFAULT_PIDFILE
        from qulo.constants import (
            PIDFILE_STR, ACTION_STR, CONFIGFILE_STR, QMASTER_DEFAULT_CONFIGFILE,
        )
        self.assertEqual(
            self.empty_init_instance.default_values[PIDFILE_STR], QMASTER_DEFAULT_PIDFILE)
        self.assertEqual(
            self.empty_init_instance.default_values[CONFIGFILE_STR], QMASTER_DEFAULT_CONFIGFILE
        )
        with self.assertRaises(KeyError):
            self.empty_init_instance.default_values[ACTION_STR]

    @patch("qulo.conf.QuloDConf._post_process_configuration")
    @patch("qulo.conf.QMasterConf._graphite_from_config_file")
    def test_post_process_configuration_calls_graphite_from_config_file(
            self, pgraphite_from_config_file, ppost_process_configuration):
        self.empty_init_instance._post_process_configuration()
        pgraphite_from_config_file.assert_called_once_with()

    @patch("qulo.conf.QMasterConf._graphite_from_config_file")
    @patch("qulo.conf.QuloDConf._post_process_configuration")
    def test_post_process_configuration_calls_same_method_in_QuloDConf(
            self, ppost_process_configuration, pgraphite_from_config_file):
        self.empty_init_instance._post_process_configuration()
        ppost_process_configuration.assert_called_once_with()

    def test_graphite_from_config_file_sets_graphite_attribute(self):
        with self.assertRaises(AttributeError):
            self.empty_init_instance.graphite
        self.empty_init_instance._config_file_conf = MagicMock()
        self.empty_init_instance._graphite_from_config_file()
        self.empty_init_instance.graphite

    def test_graphite_from_config_file_returns_defaults_if_missing_section(self):
        self.empty_init_instance._config_file_conf = {}
        self.empty_init_instance._graphite_from_config_file()
        graphite = self.empty_init_instance.graphite
        self.assertEqual(graphite[GRAPHITE_HOST_KEY], GRAPHITE_HOST)
        self.assertEqual(
            graphite[GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY],
            GRAPHITE_CARBON_RECEIVER_PICKLE_PORT
        )

    def test_graphite_from_config_file_give_priority_to_file_over_defaults_in_graphite_section(self):
        test_conf = {
            GRAPHITE_HOST_KEY: "winnipoj",
            GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY: 23452
        }
        self.empty_init_instance._config_file_conf = {GRAPHITE_SECTION: test_conf}
        self.empty_init_instance._graphite_from_config_file()
        graphite = self.empty_init_instance.graphite
        self.assertEqual(graphite[GRAPHITE_HOST_KEY], "winnipoj")
        self.assertEqual(graphite[GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY], 23452)

    def test_graphite_from_config_file_host_has_priority_over_default_in_graphite_section(self):
        test_conf = {GRAPHITE_HOST_KEY: "winnipeg"}
        self.empty_init_instance._config_file_conf = {GRAPHITE_SECTION: test_conf}
        self.empty_init_instance._graphite_from_config_file()
        graphite = self.empty_init_instance.graphite
        self.assertEqual(graphite[GRAPHITE_HOST_KEY], "winnipeg")
        self.assertEqual(
            graphite[GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY],
            GRAPHITE_CARBON_RECEIVER_PICKLE_PORT
        )

    def test_graphite_from_config_file_port_has_priority_over_default_in_graphite_section(self):
        test_conf = {GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY: 54545}
        self.empty_init_instance._config_file_conf = {GRAPHITE_SECTION: test_conf}
        self.empty_init_instance._graphite_from_config_file()
        graphite = self.empty_init_instance.graphite
        self.assertEqual(graphite[GRAPHITE_HOST_KEY], GRAPHITE_HOST)
        self.assertEqual(graphite[GRAPHITE_CARBON_RECEIVER_PICKLE_PORT_KEY], 54545)
        

if __name__ == "__main__":
    unittest.main()
