class TestTechnology(object):
    def test_deme_technology_is_right_format(self):
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 3
        #self.fakeDeme.publicGood = 20
        self.pop.initialPhenotypes = [0.5] * 4
        self.pop.createAndPopulateDemes()

        assert self.pop.demes[
            0].technologyLevel is self.pop.initialTechnologyLevel

        self.pop.clearDemeInfo()

        assert self.pop.demes[0].technologyLevel is not None
        assert type(self.pop.demes[0].technologyLevel) is float
        assert self.pop.demes[0].technologyLevel >= 0

        gc.collect()

    def test_deme_has_consensus_policing_level(self):
        self.fakeDeme = Dem()

        try:
            tmp = getattr(self.fakeDeme, "politicsValues")
            get = tmp['consensus']
        except AttributeError as e:
            assert False, "where is the policing consensus?"

        gc.collect()

    # def test_deme_policing_consensus_of_right_format(self, instantiateSingleIndividualsDemes):
    # 	gc.collect()

    # 	self.fakepop = instantiateSingleIndividualsDemes(2)

    # 	self.fakepop.clearDemeInfo()
    # 	self.fakepop.populationMutationMigration()
    # 	self.fakepop.update()

    # 	for dem in self.fakepop.demes:
    # 		assert dem.policingConsensus is not None, "No value in the policing consensus"
    # 		assert dem.policingConsensus >= 0, "Policing consensus shouldn't be negative"
    # 		assert type(dem.policingConsensus) is float, "Policing consensus should be float, not {0} ({1})".format(type(dem.policingConsensus),dem.policingConsensus)
    # 		if dem.demography > 0:
    # 			assert dem.policingConsensus == dem.meanPhenotypes[1], "Group size: {0}, phenotypes: {1}".format(dem.demography, [i.phenotypicValues for i in self.fakepop.individuals if i.currentDeme == dem.id])
    # 		else:
    # 			assert dem.policingConsensus == 0, "It would seem we have a format issue: deme mean phenotypes are {0}".format(dem.meanPhenotypes)

    def test_technology_fitness_function_exists(self, getFitnessParameters):
        self.indiv = Ind()
        self.indiv.phenotypicValues = [0.5, 0.2, 0.3]
        self.indiv.resourcesAmount = 5
        self.indiv.neighbours = [0, 2]

        try:
            self.pars = getFitnessParameters("technology")
            self.indiv.reproduce(
                "technology", **{
                    **{
                        'fine': 0.4,
                        'investmentReward': 0.6
                    },
                    **self.pars
                })
        except KeyError as e:
            assert False, "{0}".format(e)

        gc.collect()

    # def test_individuals_return_goods(self, getFitnessParameters):
    # 	self.indiv = Ind()

    # 	self.pars = getFitnessParameters("technology")
    # 	self.indiv.reproduce("technology", **self.pars)

    # 	assert self.indiv.punishmentFee is not None
    # 	assert type(self.indiv.punishmentFee) is float
    # 	assert self.indiv.punishmentFee >= 0

    # def test_returned_goods_get_calculated_and_in_right_format(self, instantiateSingleIndividualsDemes):
    # 	self.fakepop = instantiateSingleIndividualsDemes(2)

    # 	self.fakepop.clearDemeInfo()
    # 	self.fakepop.populationMutationMigration()
    # 	self.fakepop.update()

    # 	for dem in self.fakepop.demes:
    # 		assert dem.returnedGoods is not None, "No value in the effective public good"
    # 		assert dem.returnedGoods >= 0, "Effective public good shouldn't be negative"
    # 		assert type(dem.returnedGoods) is float, "Effective public good should be float, not {0}".format(type(dem.effectivePublicGood))

    # 		# resources = 0

    # 		# for ind in self.fakepop.individuals:
    # 		# 	if ind.currentDeme == dem:
    # 		# 		ind.

    # 		# assert dem.returnedGoods ==

    # def test_individual_returns_resources(self, getFitnessParameters):
    # 	ndemes = 3
    # 	initdemesize = 2
    # 	pars = getFitnessParameters('technology')
    # 	fitfun = 'technology'
    # 	phen = [0.5] * 3

    # 	## WHEN THERE IS NO POLICING, NO GOODS ARE RETURNED
    # 	self.fakepopNoPolicing = Pop(fit_fun=fitfun, inst='test')
    # 	self.fakepopNoPolicing.fit_fun = fitfun
    # 	self.fakepopNoPolicing.fitnessParameters = pars
    # 	self.fakepopNoPolicing.nDemes = ndemes
    # 	self.fakepopNoPolicing.initialDemeSize = initdemesize
    # 	self.fakepopNoPolicing.initialPhenotypes = phen
    # 	self.fakepopNoPolicing.migrationRate = 0
    # 	self.fakepopNoPolicing.fitnessParameters.update({'p':0})

    # 	self.fakepopNoPolicing.createAndPopulateDemes()
    # 	self.fakepopNoPolicing.clearDemeInfo()
    # 	self.fakepopNoPolicing.populationMutationMigration()
    # 	self.fakepopNoPolicing.updateDemeInfo()

    # 	collectGoods = [0] * self.fakepopNoPolicing.numberOfDemes
    # 	for ind in self.fakepopNoPolicing.individuals:
    # 		collectGoods[ind.currentDeme] += ind.resourcesAmount * ind.phenotypicValues[0]
    # 	for dem in range(self.fakepopNoPolicing.numberOfDemes):
    # 		assert self.fakepopNoPolicing.fit_fun == 'technology'
    # 		assert self.fakepopNoPolicing.fitnessParameters['p'] == 0
    # 		assert self.fakepopNoPolicing.demes[dem].progressValues['effectivePublicGood'] == self.fakepopNoPolicing.demes[dem].publicGood
    # 		assert self.fakepopNoPolicing.demes[dem].progressValues['effectivePublicGood'] == collectGoods[dem]

    # 	## WHEN THERE IS POLICING, GOODS MUST BE RETURNED
    # 	self.fakepopPolicing = Pop(fit_fun=fitfun, inst='test')
    # 	self.fakepopPolicing.fitnessParameters = pars
    # 	self.fakepopPolicing.nDemes = ndemes
    # 	self.fakepopPolicing.initialDemeSize = initdemesize
    # 	self.fakepopPolicing.initialPhenotypes = phen
    # 	self.fakepopPolicing.migrationRate = 0
    # 	self.fakepopPolicing.fitnessParameters.update({'p':0.8})

    # 	self.fakepopPolicing.createAndPopulateDemes()
    # 	self.fakepopPolicing.clearDemeInfo()
    # 	self.fakepopPolicing.populationMutationMigration()
    # 	self.fakepopPolicing.updateDemeInfo()

    # 	collectGoods = [0] * self.fakepopPolicing.numberOfDemes
    # 	for ind in self.fakepopPolicing.individuals:
    # 		collectGoods[ind.currentDeme] += ind.resourcesAmount * ind.phenotypicValues[0]
    # 	for dem in range(self.fakepopPolicing.numberOfDemes):
    # 		assert self.fakepopPolicing.demes[dem].progressValues['effectivePublicGood'] > collectGoods[dem] * (1-self.fakepopPolicing.fitnessParameters['p']), "goods are not returned after policing"

    # def test_effective_public_good_of_right_format(self, instantiateSingleIndividualsDemes):
    # 	self.fakepop = instantiateSingleIndividualsDemes(2)
    # 	self.fakepop.fit_fun = 'technology'

    # 	self.fakepop.clearDemeInfo()
    # 	self.fakepop.populationMutationMigration()
    # 	self.fakepop.updateDemeInfo()

    # 	for dem in self.fakepop.demes:
    # 		assert dem.progressValues['effectivePublicGood'] is not None, "No value in the effective public good"
    # 		assert dem.progressValues['effectivePublicGood'] >= 0, "Effective public good shouldn't be negative"
    # 		assert type(dem.progressValues['effectivePublicGood']) is float, "Effective public good should be float, not {0}".format(type(dem.effectivePublicGood))

    def test_technology_fitness_fct_returns_value(self, getFitnessParameters):
        self.ind = Ind()
        self.ind.resourcesAmount = 5
        self.pars = getFitnessParameters('technology')
        infoToAdd = {}
        infoToAdd['n'] = 10
        infoToAdd['xmean'] = [0.3]
        infoToAdd['x'] = [0.6]
        infoToAdd['fine'] = 0.2
        infoToAdd['investmentReward'] = 0.4

        try:
            self.ind.fertility('technology', **{**self.pars, **infoToAdd})
        except TypeError as e:
            if str(
                    e
            ) == "float() argument must be a string or a number, not 'NoneType'":
                assert False, "technology fonction returns nothing!"
            else:
                assert False, str(e)

        gc.collect()

    def test_technology_fitness_fct_takes_args(self, getFitnessParameters):
        self.ind = Ind()
        self.pars = getFitnessParameters('technology')
        self.ind.resourcesAmount = 1

        try:
            self.ind.fertility(
                'technology', **{
                    **{
                        'fine': 0.2,
                        'investmentReward': 0.4
                    },
                    **self.pars
                })
        except TypeError as e:
            assert False, "technology fitness function does not yet take arguments, fix this!"

        gc.collect()

    def test_initial_deme_technology_is_not_null(self):
        self.pop = Pop(inst='test/test')
        self.pop.createAndPopulateDemes()

        assert type(self.pop.demes[0].technologyLevel
                    ) is float, "initial technology level info missing"
        assert self.pop.demes[
            0].technologyLevel > 0, "technology level cannot be null or negative"

        gc.collect()

    def test_deme_technology_level_gets_updated_with_individual_investments(
            self, getFitnessParameters):
        self.pars = getFitnessParameters('technology')
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 10
        self.pop.initialPhenotypes = [0.5] * 4
        self.pop.fitnessParameters = self.pars
        self.pop.createAndPopulateDemes()

        demeTech = self.pop.demes[0].technologyLevel

        self.pop.lifecycle()
        self.pop.clearDemeInfo()

        assert demeTech != self.pop.demes[
            0].technologyLevel, "the technology level has not changed!"

        gc.collect()

    def test_public_good_gets_updated(self):
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 10
        self.pop.initialPhenotypes = [0.5] * 4
        self.pop.createAndPopulateDemes()

        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()
        self.pop.populationProduction()
        self.pop.updateDemeInfoPostProduction()

        assert type(
            self.pop.demes[0].publicGood
        ) is float, "publicGood must be created due to individual investments during reproduction"
        assert self.pop.demes[
            0].publicGood >= 0, "public good cannot be negative"

        gc.collect()

    def test_technology_updates_with_correct_number(self):
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 10
        self.pop.fit_fun = 'technology'
        self.pop.initialPhenotypes = [0.5] * 4
        self.pop.createAndPopulateDemes()

        assert self.pop.demes[
            0].technologyLevel == self.pop.initialTechnologyLevel, "wrong technology level assigned to deme when created"
        self.pop.clearDemeInfo()
        assert self.pop.demes[
            0].technologyLevel == self.pop.initialTechnologyLevel, "wrong technology level after first clearing"
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()
        self.pop.populationProduction()
        self.pop.updateDemeInfoPostProduction()
        # calculate new technology level as it should be
        publicGood = self.pop.demes[0].publicGood
        tech = self.pop.demes[0].technologyLevel
        tech_new = tech * (self.pop.fitnessParameters['atech'] +
                           ((1 - self.pop.fitnessParameters['p']) * publicGood)
                           **(1 - self.pop.fitnessParameters['betaTech'])) / (
                               1 + self.pop.fitnessParameters['btech'] * tech)
        self.pop.populationReproduction()
        self.pop.clearDemeInfo()
        assert self.pop.demes[
            0].technologyLevel == tech_new, "wrong value for new technology level."

        gc.collect()

    def test_individual_can_produce_its_own_resources(
            self, instantiateSingleIndividualsDemes, getFitnessParameters):
        self.args = getFitnessParameters('technology')
        self.pop = instantiateSingleIndividualsDemes(2)
        self.pop.fit_fun = 'technology'
        self.pop.fitnessParameters.update(self.args)
        self.pop.initialPhenotypes = [0.5] * 4
        self.pop.individualResources = 0
        self.pop.fitnessParameters['p'] = 0

        self.pop.createAndPopulateDemes()
        self.pop.individuals[0].resourcesAmount = 0

        assert hasattr(self.pop.individuals[0],
                       "produceResources"), "put your farmers to work!"
        self.resBEFORE = self.pop.individuals[0].resourcesAmount
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()
        self.ind = self.pop.individuals[0]
        self.deme = self.pop.demes[self.ind.currentDeme]
        self.ind.produceResources(
            'technology', **{
                **self.pop.fitnessParameters,
                **self.deme.progressValues
            })
        assert self.ind.resourcesAmount > self.resBEFORE, "that one did not get the point of production: it didn't increase its amount of resources!"

        gc.collect()

    def test_individual_resources_increase_with_technology(
            self, getFitnessParameters):
        #up_dict = {'civilianPublicTime': 0, 'labourForce': 10}
        phen = [0.5] * 4
        res = 0

        # First Individual
        self.ind1 = Ind()
        self.pars = getFitnessParameters('technology')
        #self.pars.update({'civilianPublicTime': 0, 'labourForce': 10, 'technologyLevel': 2})
        tech1 = 2.4
        tech2 = 5.9

        res1 = (self.pars['n']**(-self.pars['alphaResources'])) * (
            tech1**self.pars['alphaResources'])
        res2 = (self.pars['n']**(-self.pars['alphaResources'])) * (
            tech2**self.pars['alphaResources'])
        assert res1 < res2

        self.pars.update({'tech': tech1, 'p': 0})
        self.ind1.phenotypicValues = phen
        self.ind1.resourcesAmount = res

        self.ind1.pars = self.pars
        self.ind1.produceResources('technology', **self.ind1.pars)
        assert self.ind1.resourcesAmount == res1

        # Second Individual
        self.ind2 = Ind()
        self.pars.update({'tech': tech2})
        self.ind2.phenotypicValues = phen
        self.ind2.resourcesAmount = res

        self.ind2.pars = self.pars
        self.ind2.produceResources('technology', **self.ind2.pars)
        assert self.ind2.resourcesAmount == res2

        assert self.ind1.resourcesAmount < self.ind2.resourcesAmount, "ind1 knows 2 and gets {0}, ind2 knows 5 and gets {1}, when really those with more knowledge should get more resources, all else being equal".format(
            self.ind1.resourcesAmount, self.ind2.resourcesAmount)

        gc.collect()

    def test_group_labour_force_is_calculated_and_given_to_individual_instance(
            self):
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 3
        self.pop.initialDemeSize = 20
        self.pop.createAndPopulateDemes()
        self.deme = self.pop.demes[0]
        assert hasattr(self.deme, "progressValues"), "make dict"
        assert type(self.deme.progressValues) is dict
        progressKeys = ["fine", "investmentReward"]
        for key in progressKeys:
            assert key in self.deme.progressValues

        self.pop.clearDemeInfo()
        for pheno in self.pop.demes[0].meanPhenotypes:
            assert pheno is not None, "none phenotypes before migration"
        self.pop.populationMutationMigration()
        for pheno in self.pop.demes[0].meanPhenotypes:
            assert pheno is not None, "none phenotypes before update"
        self.pop.updateDemeInfoPreProduction()
        self.pop.populationProduction()
        self.pop.updateDemeInfoPostProduction()

        self.demeAFTER = self.pop.demes[0]
        for key in progressKeys:
            assert self.demeAFTER.progressValues[key] is not None
        # deme labour force = total private time: (demography - nleaders)(1-T1) + nleaders(1-T2)
        # where T1 and T2 is effective time spent in debate by civilian and leader respectively

        gc.collect()

    def test_production_increase_function(self):
        #pars = getFitnessParameters('technology')
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 5

        self.pop.createAndPopulateDemes()
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()
        self.pars = self.pop.fitnessParameters

        for ind in self.pop.individuals:
            deme = self.pop.demes[ind.currentDeme]
            infoToAdd = {}
            infoToAdd["tech"] = deme.technologyLevel
            infoToAdd["n"] = deme.demography
            infoToAdd["xmean"] = deme.meanPhenotypes
            infoToAdd["pg"] = deme.publicGood
            infoToAdd["x"] = ind.phenotypicValues
            # assert deme.progressValues["labourForce"] is not None, "labour force is none!"
            # assert deme.progressValues["labourForce"] != 0, "labour force is null!"
            assert deme.technologyLevel is not None, "technology is none!"
            fine = deme.publicGood * self.pars['p'] / deme.demography
            benef = ((deme.publicGood * (1 - self.pars['p']))**
                     self.pars["betaTech"]) / deme.demography
            resourcesProduced = deme.demography**(
                -self.pars['alphaResources']
            ) * infoToAdd['tech']**self.pars['alphaResources']

            ind.produceResources(self.pop.fit_fun, **{
                **self.pop.fitnessParameters,
                **infoToAdd
            })
            assert ind.resourcesAmount == resourcesProduced, "ind produced {0} instead of {1}".format(
                ind.resourcesAmount, payoff)

            gc.collect()

    def test_fitness_function_returns_correct_value(self):
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.numberOfDemes = 3
        self.pop.initialDemeSize = 5
        self.pop.createAndPopulateDemes()
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()
        self.pop.populationProduction()
        self.pop.updateDemeInfoPostProduction()

        for ind in self.pop.individuals:
            #assert self.pop.demes[ind.currentDeme].progressValues['technologyLevel'] > 1, "technology level too low: {0}".format(self.pop.demes[ind.currentDeme].progressValues['technologyLevel'])
            #assert ind.resourcesAmount > 0, "not enough resources to reproduce: {0}".format(ind.resourcesAmount)
            infoToAdd = {}
            infoToAdd['n'] = self.pop.demes[ind.currentDeme].demography
            infoToAdd['xmean'] = self.pop.demes[ind.currentDeme].meanPhenotypes
            infoToAdd['tech'] = self.pop.demes[ind.currentDeme].technologyLevel
            infoToAdd['pg'] = self.pop.demes[ind.currentDeme].publicGood
            infoToAdd['x'] = ind.phenotypicValues
            ind.reproduce(
                'technology', **{
                    **self.pop.fitnessParameters,
                    **infoToAdd,
                    **self.pop.demes[ind.currentDeme].progressValues
                })

            fine = infoToAdd['pg'] * self.pop.fitnessParameters[
                'p'] / infoToAdd['n']
            benef = ((infoToAdd['pg'] * (1 - self.pop.fitnessParameters['p']))
                     **self.pop.fitnessParameters["betaTech"]) / infoToAdd['n']
            payoff = (1 - self.pop.fitnessParameters['q']) * (
                1 - infoToAdd['x'][0]
            ) * ind.resourcesAmount + self.pop.fitnessParameters['q'] * (
                (1 - infoToAdd['x'][0]) * ind.resourcesAmount - fine) + benef
            w = (self.pop.fitnessParameters['rb'] + payoff) / (
                1 + self.pop.fitnessParameters['gamma'] * infoToAdd['n'])
            assert ind.fertilityValue == w, "wrong fitness calculation for individual, should return {0}".format(
                w)

        gc.collect()

    def test_individuals_reproduce_after_production(self,
                                                    getFitnessParameters):
        self.params = getFitnessParameters('technology')
        self.params.update({'p': 0, 'tech': 10.5})
        self.ind = Ind()
        self.ind.neighbours = [1, 2]
        self.ind.phenotypicValues = [0.5] * 3

        res = (self.params['n']**(-self.params['alphaResources'])) * (
            self.params['tech']**self.params['alphaResources'])
        assert res > 0, "no resources produced"
        fine = self.params['pg'] * self.params['p'] / self.params['n']
        benef = ((self.params['pg'] * (1 - self.params['p']))**
                 self.params["betaTech"]) / self.params['n']
        payoff = (1 - self.params['q']) * (
            1 - self.ind.phenotypicValues[0]) * res + self.params['q'] * (
                (1 - self.ind.phenotypicValues[0]) * res - fine) + benef
        f = (self.params["rb"] +
             payoff) / (1 + self.params["gamma"] * self.params["n"])

        self.ind.produceResources('technology', **self.params)
        self.ind.reproduce(
            'technology', **{
                **{
                    'fine': fine,
                    'investmentReward': benef
                },
                **self.params
            })
        assert self.ind.fertilityValue == f, "wrong fertility value"

        self.ind2 = Ind()
        self.ind2.neighbours = [1, 2]
        self.ind2.phenotypicValues = [0.5] * 3
        res2 = (self.params['n']**(-self.params['alphaResources'])) * (
            self.params['tech']**self.params['alphaResources'])
        fine2 = self.params['pg'] * self.params['p'] / self.params['n']
        benef2 = ((self.params['pg'] * (1 - self.params['p']))**
                  self.params["betaTech"]) / self.params['n']
        payoff2 = (1 - self.params['q']) * (
            1 - self.ind2.phenotypicValues[0]) * res2 + self.params['q'] * (
                (1 - self.ind2.phenotypicValues[0]) * res2 - fine2) + benef2
        assert self.params['q'] * (self.params['pg'] *
                                   self.params['p']) / self.params['n'] == 0
        assert (1 -
                self.params['q'] * self.params['d'] * self.params['p']) == 1
        assert (1 - self.ind.phenotypicValues[0]) == 0.5
        assert res2 > 0, "no resources produced"
        f2 = (self.params["rb"] +
              payoff2) / (1 + self.params["gamma"] * self.params["n"])
        assert f2 == f, "all being equal, the fertility values should be the same"

        self.ind2.produceResources('technology', **self.params)
        self.ind2.reproduce(
            'technology', **{
                **{
                    'fine': fine2,
                    'investmentReward': benef2
                },
                **self.params
            })
        assert self.ind2.fertilityValue == f, "wrong fertility value"

        self.params.update({'p': 0.7})
        self.ind3 = Ind()
        self.ind3.neighbours = [1, 2]
        self.ind3.phenotypicValues = [0.5] * 3
        res3 = (self.params['n']**(-self.params['alphaResources'])) * (
            self.params['tech']**self.params['alphaResources'])
        fine3 = self.params['pg'] * self.params['p'] / self.params['n']
        benef3 = ((self.params['pg'] * (1 - self.params['p']))**
                  self.params["betaTech"]) / self.params['n']
        payoff3 = (1 - self.params['q']) * (
            1 - self.ind3.phenotypicValues[0]) * res3 + self.params['q'] * (
                (1 - self.ind3.phenotypicValues[0]) * res3 - fine3) + benef3
        assert res3 > 0, "no resources produced"
        f3 = (self.params["rb"] +
              payoff3) / (1 + self.params["gamma"] * self.params["n"])

        self.ind3.produceResources('technology', **self.params)
        self.ind3.reproduce(
            'technology', **{
                **{
                    'fine': fine3,
                    'investmentReward': benef3
                },
                **self.params
            })
        assert self.ind3.fertilityValue == f3, "wrong fertility value"

        gc.collect()
class TestSimpleRun(object):
    def test_initialisation_file_exists(self):
        # Claire wants to model the evolution of a structured population.
        # She provides the initial conditions for the run: number of demes, initial deme size, number of generations in a file called initialisation.txt
        self.fileslist = os.listdir('test/test')
        assert INITIALISATION_FILE in self.fileslist, "Initialisation file not found in test/test"

    def test_initialisation_parameters_listed(self):
        self.pathToFile = fman.getPathToFile(filename=INITIALISATION_FILE,
                                             dirname='test/test')
        for par in ['numberOfDemes', 'initialDemeSize', 'numberOfGenerations']:
            fman.searchFile(self.pathToFile, par)

    def test_initialisation_values_provided(self):
        self.pathToFile = fman.getPathToFile(filename=INITIALISATION_FILE,
                                             dirname='test/test')

        self.pars = fman.extractColumnFromFile(self.pathToFile, 1, int)
        for par in self.pars:
            assert type(
                par
            ) is int, "Did you insert a non integer value for deme number, deme size or generation number?"
            assert par > 0, "Did you insert a negative value for deme number, deme size or generation number?"

    def test_parameter_file_exists(self):
        # Claire provides parameters needed by the program to run functions in a file called parameters.txt
        self.dirpath = os.getcwd()
        self.fileslist = os.listdir('{0}/{1}'.format(self.dirpath,
                                                     'test/test'))
        assert PARAMETER_FILE in self.fileslist, "Parameter file not found in {0}".format(
            self.dirpath)

    def test_initial_phenotypes_file_exists(self):
        self.dirpath = os.getcwd()
        self.fileslist = os.listdir('{0}/{1}'.format(self.dirpath,
                                                     'test/test'))
        assert INITIAL_PHENOTYPES_FILE in self.fileslist, "Initial phenotypes file not found in {0}".format(
            self.dirpath)

    def test_fitness_parameters_file_exists(self):
        self.dirpath = os.getcwd()
        self.fileslist = os.listdir('{0}/{1}'.format(self.dirpath,
                                                     'test/test'))
        assert FITNESS_PARAMETERS_FILE in self.fileslist, "Fitness parameter file not found in {0}".format(
            self.dirpath)

    def test_initial_phenotypes_format(self):
        self.pathToFile = fman.getPathToFile(filename=INITIAL_PHENOTYPES_FILE,
                                             dirname='test/test')
        with open(self.pathToFile) as f:
            lines = [float(x) for x in f.readlines()]
            for line in lines:
                assert type(line) is float
                assert 0 <= line <= 1

    # Unfortunately, it would seem that she created a a well-mixed population, i.e. the population is not structured in demes / number of demes = 1.
    # This program is not meant for well-mixed populations, and tells Claire so.
    def test_simulations_only_run_on_structured_populations(
            self, clearOutputFiles):
        self.population = Pop(inst='test/test')
        setattr(self.population, "numberOfDemes", 1)
        setattr(self.population, "numberOfGenerations", 4)
        try:
            self.population.runSimulation()
        except ValueError as e:
            assert str(
                e
            ) == 'This program runs simulations on well-mixed populations only. "numberOfDemes" in initialisation.txt must be > 1', "Explain why the program fails!, not '{0}'".format(
                e)
        else:
            assert False, "You cannot let people run simulations on well-mixed populations (only {0} deme)!".format(
                self.population.numberOfDemes)
            clearOutputFiles('test/test/')

    # She then changes the number of demes so that the population is structured into multiple demes.
    # Unfortunately, she asks the program to run a simulation with a 'gibberish' fitness function, which is not yet known by the program. The programs tells her to add the function in the fitness function dictionary

    def test_program_requires_valid_fitness_function(self, clearOutputFiles):
        self.population = Pop(fit_fun="gibberish", inst='test/test')
        setattr(self.population, "numberOfGenerations", 4)
        try:
            self.population.runSimulation()
        except KeyError as e:
            assert str(e).replace(
                "'", ""
            ) == 'Fitness function "gibberish" unknown. Add it to the functions in fitness.py', "Explain why the program fails!, not '{0}'".format(
                e)
        else:
            assert False, "The program should return an error message when trying to run simulations with unknown fitness function".format(
                self.population.numberOfDemes)
            clearOutputFiles('test/test/')

        # She runs the program:
    def test_population_is_initialised_with_right_values(
            self, objectAttributesExist, objectAttributesValues):
        # First, the population is initialised according to the initialisation settings
        self.pathToFile = fman.getPathToFile(filename=INITIALISATION_FILE,
                                             dirname='test/test')
        self.attributeNames = fman.extractColumnFromFile(
            self.pathToFile, 0, str)
        self.attributeValues = fman.extractColumnFromFile(
            self.pathToFile, 1, int)
        self.population = Pop(inst='test/test')

        testAttr, whichAttr = objectAttributesExist(self.population,
                                                    self.attributeNames)
        assert testAttr, "Population does not have attribute(s) {0}".format(
            whichAttr)
        testVal, attributes, expected, observed = objectAttributesValues(
            self.population, self.attributeNames, self.attributeValues)
        assert testVal, "Population has {1}={2} instead of {3}".format(
            attributes, expected, observed)

    def test_program_writes_output_for_x_generations(self, runSim,
                                                     clearOutputFiles):
        # Second, the population evolves over x generations following the iteration function
        # After the run, the results are saved in a folder called "res"
        ngen = runSim(5)

        with open('test/test/out_phenotypes.txt') as fp, open(
                'test/test/out_demography.txt') as fd:
            self.fplines = fp.readlines()
            self.fdlines = fd.readlines()

        #self.pathToFile = fman.getPathToFile(filename=INITIALISATION_FILE, dirname=PARAMETER_FOLDER)
        #self.attributeNames = fman.extractColumnFromFile(self.pathToFile, 0, str)
        #self.attributeValues = fman.extractColumnFromFile(self.pathToFile, 1, int)

        assert len(self.fplines) == ngen, "wrong # of generations, {0}".format(
            self.fplines)
        assert len(self.fdlines) == ngen

        clearOutputFiles('test/test/')

    def test_program_writes_non_empty_output(self, runSim, clearOutputFiles):
        runSim()

        with open('test/test/out_phenotypes.txt') as fp, open(
                'test/test/out_demography.txt') as fd:
            self.resfp = [len(line.strip()) for line in fp.readlines()]
            self.resfd = [len(line.strip()) for line in fd.readlines()]

        assert sum(self.resfp) > 0
        assert sum(self.resfd) > 0

        clearOutputFiles('test/test/')

    # She goes to the output folder and sees that two files have been written by the program, one with the mean phenotypes and the other with the mean deme size
    def test_program_writes_all_variable_files(self, runSim, clearOutputFiles):
        runSim()

        allOutput = glob.glob('test/test/out_*.txt')
        assert len(
            allOutput
        ) == 5, f"did not find all output files with pattern: {'out_*.txt'} in {allOutput}"
        assert 'test/test/out_phenotypes.txt' in allOutput, f"did not find phenotypes output file in {allOutput}"
        assert 'test/test/out_demography.txt' in allOutput, f"did not find demography output file in {allOutput}"
        assert 'test/test/out_technology.txt' in allOutput, f"did not find technology output file in {allOutput}"
        assert 'test/test/out_resources.txt' in allOutput, f"did not find resources output file in {allOutput}"
        assert 'test/test/out_consensus.txt' in allOutput, f"did not find consensus time output file in {allOutput}"

        clearOutputFiles('test/test/')

    # she opens the phenotypes file, and check that the number of phenotypes is right and that the value seem correct.
    def test_phenotype_file_has_correct_output(self, runSim, clearOutputFiles):
        runSim(mutRate=0)

        with open('test/test/initial_phenotypes.txt', 'r') as initphen:
            phens = [float(x) for x in initphen.readlines()]
            nphens = len(phens)

        with open('test/test/out_phenotypes.txt') as outphenfile:
            for line in outphenfile:
                getphens = [float(x) for x in line.split(',')][0:nphens]
                assert len(
                    getphens
                ) == nphens, "wrong number of phenotypes printed in line: {0}".format(
                    line)
                assert getphens == phens, "this line is not identical to initial phenotypes even though mutaiton rate is null"

        clearOutputFiles('test/test/')

    #she then moves on to verify every single output file: first, demography...
    def test_demography_file_has_correct_output(self, pseudorandom, runSim,
                                                clearOutputFiles):
        pseudorandom(69)
        self.pop = Pop(inst='test/test')
        self.pop.mutationRate = 0.1
        self.pop.numberOfDemes = 5
        self.pop.initialDemeSize = 8
        self.pop.fitnessParameters.clear()
        self.pop.fitnessParameters.update({
            "fb": 10,
            "b": 0.5,
            "c": 0.05,
            "gamma": 0.01
        })
        self.pop.createAndPopulateDemes()
        expDemog = []
        for gen in range(5):
            self.pop.lifecycle()
            assert self.pop.numberOfDemes == 5
            expDemog.append(self.pop.demography / self.pop.numberOfDemes)

        pseudorandom(69)
        runSim()
        obsDemog = fman.extractColumnFromFile('test/test/out_demography.txt',
                                              0, float)

        assert obsDemog == expDemog, "unexpected value of group size has been printed"

        clearOutputFiles('test/test/')

    # next, technology...
    def test_technology_file_has_correct_output(self, pseudorandom, runSim,
                                                clearOutputFiles):
        parameters = {
            "gamma": 0.01,
            "p": 0.6,
            "q": 0.9,
            "d": 0.2,
            "productionTime": 1,
            "alphaResources": 0.6,
            "rb": 10,
            "atech": 2,
            "btech": 0.2,
            "betaTech": 0.6
        }
        pseudorandom(85)
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.mutationRate = 0.1
        self.pop.numberOfDemes = 5
        self.pop.initialDemeSize = 8
        self.pop.fitnessParameters.clear()
        self.pop.fitnessParameters.update(parameters)
        self.pop.createAndPopulateDemes()
        expTechno = []
        for gen in range(5):
            self.pop.lifecycle()
            assert self.pop.numberOfDemes == 5
            collectDemeTech = []
            for deme in self.pop.demes:
                collectDemeTech.append(deme.technologyLevel)
            expTechno.append(sum(collectDemeTech) / len(collectDemeTech))

        pseudorandom(85)
        runSim(fun='technology', pars=parameters)
        obsTechno = fman.extractColumnFromFile('test/test/out_technology.txt',
                                               0, float)

        assert obsTechno == expTechno, "unexpected value of technology level has been printed"

        clearOutputFiles('test/test/')

    # then, resources...
    def test_resources_file_has_correct_output(self, pseudorandom, runSim,
                                               clearOutputFiles):
        parameters = {
            "gamma": 0.01,
            "p": 0.6,
            "q": 0.9,
            "d": 0.2,
            "productionTime": 1,
            "alphaResources": 0.6,
            "rb": 10,
            "atech": 2,
            "btech": 0.2,
            "betaTech": 0.6
        }
        pseudorandom(54)
        self.pop = Pop(fit_fun='technology', inst='test/test')
        self.pop.mutationRate = 0.1
        self.pop.numberOfDemes = 5
        self.pop.initialDemeSize = 8
        self.pop.fitnessParameters.clear()
        self.pop.fitnessParameters.update(parameters)
        self.pop.createAndPopulateDemes()
        expTotRes = []
        for gen in range(5):
            self.pop.lifecycle()
            assert self.pop.numberOfDemes == 5
            collectDemeTotRes = []
            for deme in self.pop.demes:
                collectDemeTotRes.append(deme.totalResources)
            expTotRes.append(sum(collectDemeTotRes) / len(collectDemeTotRes))

        pseudorandom(54)
        runSim(fun='technology', pars=parameters)
        obsTotRes = fman.extractColumnFromFile('test/test/out_resources.txt',
                                               0, float)

        assert obsTotRes == expTotRes, "unexpected value of total resources has been printed"

        clearOutputFiles('test/test/')

    # finally, consensus...
    def test_consensus_file_has_correct_output(self, pseudorandom, runSim,
                                               clearOutputFiles):
        parameters = {
            "gamma": 0.01,
            "aconsensus": 3,
            "bconsensus": 2,
            "epsilon": 0.01,
            "aquality": 4,
            "alphaquality": 0.9,
            "alphaResources": 0.8,
            "techcapital": 50,
            "rb": 10
        }
        pseudorandom(22)
        self.pop = Pop(fit_fun='debate', inst='test/test')
        self.pop.mutationRate = 0.1
        self.pop.numberOfDemes = 5
        self.pop.initialDemeSize = 8
        self.pop.fitnessParameters.clear()
        self.pop.fitnessParameters.update(parameters)
        self.pop.createAndPopulateDemes()
        expCons = []
        for gen in range(5):
            self.pop.lifecycle()
            assert self.pop.numberOfDemes == 5
            collectDemeCons = []
            for deme in self.pop.demes:
                collectDemeCons.append(deme.politicsValues["consensusTime"])
            expCons.append(sum(collectDemeCons) / len(collectDemeCons))

        pseudorandom(22)
        runSim(fun='debate', pars=parameters)
        obsCons = fman.extractColumnFromFile('test/test/out_consensus.txt', 0,
                                             float)

        assert obsCons == expCons, "unexpected value of total resources has been printed"

        clearOutputFiles('test/test/')

    # she tries a new set of parameters for which the population size goes to zero. The prgoram exits with a warning
    def test_simulation_stops_with_information_message_when_population_extinct(
            self, runSim, clearOutputFiles):
        try:
            runSim(fb=0)
        except TypeError as e:
            assert False, "The program should exit with information message when population goes extinct!"

        clearOutputFiles('test/test/')

    # Satisfied, she goes to sleep.
class TestSocialClassesFeature(object):
    def test_individual_can_become_leader(self):
        self.ind = Ind()
        assert hasattr(self.ind, "leader")

    def test_demes_have_a_number_of_elected_leaders(self):
        self.deme = Dem()
        assert hasattr(self.deme, "numberOfLeaders")

    def test_social_class_function_in_fitness_and_progress(self):
        assert 'socialclass' in fitness.functions, "create social class fitness function"
        assert 'socialclass' in progress.functions, "create social class progress function"

    def test_elections_take_place_in_demes(self, pseudorandom):
        pseudorandom(23)
        self.pop = Pop(fit_fun='socialclass', inst='test/test')
        self.pop.numberOfDemes = 2
        self.pop.initialDemeSize = 500
        self.pop.migrationRate = 0

        self.pop.createAndPopulateDemes()
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()

        for deme in self.pop.demes:
            assert deme.numberOfLeaders is not None
            assert deme.numberOfLeaders / deme.demography == pytest.approx(
                deme.meanPhenotypes[3], 1.4)

    def test_individuals_get_assigned_a_role_during_elections(
            self, pseudorandom, instantiateSingleDemePopulation):
        #proportionOfLeaders = rd.random()
        pseudorandom(0)
        self.nIndividuals = 1000
        self.pop = instantiateSingleDemePopulation(self.nIndividuals)
        proportionOfLeaders = 0.896

        leaderCount = 0
        for ind in self.pop.individuals:
            assert hasattr(
                ind, "ascend"
            ), "individual needs to be able to ascend social ladder"
            ind.ascend(leadProp=proportionOfLeaders)
            if ind.leader:
                leaderCount += 1

        stat1, pval1 = scistats.ttest_1samp([1] * leaderCount + [0] *
                                            (self.nIndividuals - leaderCount),
                                            proportionOfLeaders)
        assert pval1 > 0.05, "T-test mean failed. Observed: {0}, Expected: {1}".format(
            leaderCount / self.nIndividuals, proportionOfLeaders)
        self.test = scistats.binom_test(leaderCount,
                                        self.nIndividuals,
                                        proportionOfLeaders,
                                        alternative="two-sided")
        assert self.test > 0.05, "Success rate = {0} when proportion of leaders = {1}".format(
            leaderCount / self.nIndividuals, proportionOfLeaders)

        gc.collect()

    def test_deme_gets_number_of_leaders(self, pseudorandom,
                                         getFitnessParameters):
        pseudorandom(10)
        self.pop = Pop(fit_fun='socialclass', inst='test/test')
        self.pop.fitnessParameters = getFitnessParameters('socialclass')
        self.pop.numberOfDemes = 3
        self.pop.initialDemeSize = 1000
        self.pop.initialPhenotypes = [0.1, 0.2, 0.3, 0.6]
        self.pop.migrationRate = 0
        self.pop.mutationRate = 0

        self.pop.createAndPopulateDemes()
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()

        plead = self.pop.initialPhenotypes[3]

        for deme in self.pop.demes:
            nlead = deme.numberOfLeaders
            assert nlead is not None
            assert type(nlead) is int
            assert nlead >= 0
            assert pytest.approx(nlead / deme.demography, 0.1) == plead
            stat1, pval1 = scistats.ttest_1samp(
                [1] * nlead + [0] * (deme.demography - nlead), plead)
            #assert pval1 > 0.05, "T-test mean failed. Observed: {0}, Expected: {1}".format(nlead/deme.demography, plead)
            self.test = scistats.binom_test(nlead,
                                            deme.demography,
                                            plead,
                                            alternative="two-sided")
            assert self.test > 0.05, "Success rate = {0} when proportion of leaders = {1}".format(
                nlead / deme.demography, plead)

        gc.collect()

    def test_deme_number_of_leaders_is_number_of_individuals_with_that_role(
            self, pseudorandom, getFitnessParameters):
        pseudorandom(0)
        self.pop = Pop(fit_fun='socialclass', inst='test/test')
        self.pop.fitnessParameters = getFitnessParameters('socialclass')
        self.pop.numberOfDemes = 3
        self.pop.initialDemeSize = 10
        self.pop.initialPhenotypes = [0.1, 0.2, 0.3, 0.6]
        self.pop.migrationRate = 0
        self.pop.mutationRate = 0

        self.pop.createAndPopulateDemes()
        self.pop.clearDemeInfo()
        self.pop.populationMutationMigration()
        self.pop.updateDemeInfoPreProduction()

        leaderCountPerDeme = [0] * self.pop.numberOfDemes
        for ind in self.pop.individuals:
            leaderCountPerDeme[ind.currentDeme] += ind.leader

        for deme in range(self.pop.numberOfDemes):
            nl = self.pop.demes[deme].numberOfLeaders
            assert nl != 10, "all individuals in deme have been elected leaders when proportion should be about: " + self.pop.initialPhenotypes[
                3]
            assert leaderCountPerDeme[
                deme] == nl, "deme counts {0} leaders when there are {1}".format(
                    nl, leaderCountPerDeme[deme])

    def test_social_class_determines_fitness(self, getFitnessParameters):
        pars = getFitnessParameters('socialclass')
        self.firstInd = Ind()
        self.firstInd.resourcesAmount = 1
        self.firstInd.leader = bool(1)
        self.firstInd.neighbours = [1, 2]
        pars.update({'leadership': self.firstInd.leader})
        self.firstInd.reproduce(fun_name='socialclass', **pars)
        self.secndInd = Ind()
        self.secndInd.resourcesAmount = 1
        self.secndInd.leader = bool(0)
        self.secndInd.neighbours = [1, 2]
        pars.update({'leadership': self.secndInd.leader})
        self.secndInd.reproduce(fun_name='socialclass', **pars)

        assert self.firstInd.fertilityValue != self.secndInd.fertilityValue, "leaders and commoners should not get the same fitness"

    def test_fitness_function_runs_at_population_level(self):
        self.pop = Pop(fit_fun='socialclass', inst='test/test')
        self.pop.numberOfDemes = 3
        self.pop.initialDemeSize = 5
        self.pop.initialPhenotypes = [0.9, 0.7, 0.5, 0.3]
        self.pop.createAndPopulateDemes()

        try:
            self.pop.lifecycle()
        except ValueError as e:
            assert False, str(e)