Skip to content

Commit 49f83b9

Browse files
committed
Merge pull request #588 from Axelrod-Python/581
581 - Improving test coverage
2 parents 7e19f65 + 3119c9d commit 49f83b9

15 files changed

+154
-93
lines changed

axelrod/_strategy_utils.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,25 +96,20 @@ def __init__(self, func):
9696
self.cache = {}
9797

9898
def __call__(self, *args):
99-
if not isinstance(args, collections.Hashable):
100-
# uncacheable. a list, for instance.
101-
# better to not cache than blow up.
99+
try:
100+
try:
101+
return self.cache[args]
102+
except KeyError:
103+
value = self.func(*args)
104+
self.cache[args] = value
105+
return value
106+
except TypeError:
102107
return self.func(*args)
103-
if args in self.cache:
104-
return self.cache[args]
105-
else:
106-
value = self.func(*args)
107-
self.cache[args] = value
108-
return value
109108

110109
def __repr__(self):
111110
"""Return the function's docstring."""
112111
return self.func.__doc__
113112

114-
def __get__(self, obj, objtype):
115-
"""Support instance methods."""
116-
return functools.partial(self.__call__, obj)
117-
118113

119114
@Memoized
120115
def recursive_thue_morse(n):

axelrod/deterministic_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __setitem__(self, key, value):
5050
"""Overrides the UserDict.__setitem__ method in order to validate
5151
the key/value and also to set the turns attribute"""
5252
if not self.mutable:
53-
raise ValueError('Cannot update cache unles mutable is True.')
53+
raise ValueError('Cannot update cache unless mutable is True.')
5454

5555
if not self._is_valid_key(key):
5656
raise ValueError(

axelrod/match_generator.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
class MatchGenerator(object):
99

10-
clone_opponents = True
11-
1210
def __init__(self, players, turns, game, repetitions):
1311
"""
1412
A class to generate matches. This is used by the Tournament class which
@@ -37,12 +35,9 @@ def opponents(self):
3735

3836
@opponents.setter
3937
def opponents(self, players):
40-
if self.clone_opponents:
41-
opponents = []
42-
for player in players:
43-
opponents.append(player.clone())
44-
else:
45-
opponents = players
38+
opponents = []
39+
for player in players:
40+
opponents.append(player.clone())
4641
self._opponents = opponents
4742

4843
def __len__(self):
@@ -57,8 +52,6 @@ def build_single_match_params(self):
5752

5853
class RoundRobinMatches(MatchGenerator):
5954

60-
clone_opponents = True
61-
6255
def build_match_chunks(self, noise=0):
6356
"""
6457
A generator that returns player index pairs and match parameters for a
@@ -110,8 +103,6 @@ def estimated_size(self):
110103

111104
class ProbEndRoundRobinMatches(RoundRobinMatches):
112105

113-
clone_opponents = True
114-
115106
def __init__(self, players, prob_end, game, repetitions):
116107
"""
117108
A class that generates matches for which the players do not

axelrod/moran.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __init__(self, players, turns=100, noise=0, deterministic_cache=None):
4040
self.set_players()
4141
self.score_history = []
4242
self.winning_strategy_name = None
43-
if deterministic_cache:
43+
if deterministic_cache is not None:
4444
self.deterministic_cache = deterministic_cache
4545
else:
4646
self.deterministic_cache = DeterministicCache()

axelrod/plot.py

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from numpy.linalg import LinAlgError
21
from numpy import arange, median, nan_to_num
32
from warnings import warn
43

@@ -27,23 +26,6 @@ def __init__(self, result_set):
2726
self.result_set = result_set
2827
self.matplotlib_installed = matplotlib_installed
2928

30-
# Abstract Box and Violin plots
31-
32-
def _boxplot(self, data, names, title=None):
33-
"""For making boxplots."""
34-
if not self.matplotlib_installed:
35-
return None
36-
nplayers = self.result_set.nplayers
37-
width = max(nplayers / 3, 12)
38-
height = width / 2
39-
figure = plt.figure(figsize=(width, height))
40-
plt.boxplot(data)
41-
plt.xticks(self._boxplot_xticks_locations, names, rotation=90)
42-
plt.tick_params(axis='both', which='both', labelsize=8)
43-
if title:
44-
plt.title(title)
45-
return figure
46-
4729
def _violinplot(self, data, names, title=None):
4830
"""For making violinplots."""
4931
if not self.matplotlib_installed:
@@ -83,14 +65,7 @@ def boxplot(self, title=None):
8365
"""For the specific mean score boxplot."""
8466
data = self._boxplot_dataset
8567
names = self._boxplot_xticks_labels
86-
try:
87-
figure = self._violinplot(data, names, title=title)
88-
except LinAlgError:
89-
# Matplotlib doesn't handle single point distributions well
90-
# in violin plots. Should be fixed in next release:
91-
# https://github.com/matplotlib/matplotlib/pull/4816
92-
# Fall back to boxplot
93-
figure = self._boxplot(data, names, title=title)
68+
figure = self._violinplot(data, names, title=title)
9469
return figure
9570

9671
@property
@@ -112,14 +87,7 @@ def winplot(self, title=None):
11287
return None
11388

11489
data, names = self._winplot_dataset
115-
try:
116-
figure = self._violinplot(data, names, title)
117-
except LinAlgError:
118-
# Matplotlib doesn't handle single point distributions well
119-
# in violin plots. Should be fixed in next release:
120-
# https://github.com/matplotlib/matplotlib/pull/4816
121-
# Fall back to boxplot
122-
figure = self._boxplot(data, names, title)
90+
figure = self._violinplot(data, names, title)
12391
# Expand ylim a bit
12492
maximum = max(max(w) for w in data)
12593
plt.ylim(-0.5, 0.5 + maximum)
@@ -165,14 +133,7 @@ def lengthplot(self, title=None):
165133
"""For the specific match length boxplot."""
166134
data = self._lengthplot_dataset
167135
names = self._boxplot_xticks_labels
168-
try:
169-
figure = self._violinplot(data, names, title=title)
170-
except LinAlgError:
171-
# Matplotlib doesn't handle single point distributions well
172-
# in violin plots. Should be fixed in next release:
173-
# https://github.com/matplotlib/matplotlib/pull/4816
174-
# Fall back to boxplot
175-
figure = self._boxplot(data, names, title=title)
136+
figure = self._violinplot(data, names, title=title)
176137
return figure
177138

178139
# Payoff heatmaps
@@ -242,11 +203,7 @@ def stackplot(self, eco, title=None):
242203
if not self.matplotlib_installed:
243204
return None
244205

245-
if type(eco) is list:
246-
warn("""Passing the population sizes as an argument is deprecated and will be removed, please pass the Ecosystem directly""")
247-
populations = eco
248-
else:
249-
populations = eco.population_sizes
206+
populations = eco.population_sizes
250207

251208
figure, ax = plt.subplots()
252209
turns = range(len(populations))

axelrod/strategies/apavlov.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,17 @@ def strategy(self, opponent):
5252
if len(self.history) % 6 in [0, 1]:
5353
return C
5454
#TFT
55-
return D if opponent.history[-1:] == [D] else C
55+
if opponent.history[-1:] == [D]:
56+
return D
5657
if self.opponent_class == "PavlovD":
5758
# Return D then C for the period
5859
if len(self.history) % 6 == 0:
5960
return D
60-
return C
6161
if self.opponent_class == "Cooperative":
6262
#TFT
63-
return D if opponent.history[-1:] == [D] else C
63+
if opponent.history[-1:] == [D]:
64+
return D
65+
return C
6466

6567
def reset(self):
6668
Player.reset(self)

axelrod/strategies/qlearner.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(self):
3737
self.prev_action = random_choice()
3838
self.history = []
3939
self.score = 0
40-
self.Qs = OrderedDict({'': OrderedDict(zip([C, D], [0, 0])) })
40+
self.Qs = OrderedDict({'': OrderedDict(zip([C, D], [0, 0]))})
4141
self.Vs = OrderedDict({'': 0})
4242
self.prev_state = ''
4343

@@ -53,10 +53,7 @@ def strategy(self, opponent):
5353
self.Qs[state] = OrderedDict(zip([C, D], [0, 0]))
5454
self.Vs[state] = 0
5555
self.perform_q_learning(self.prev_state, state, self.prev_action, reward)
56-
if state not in self.Qs:
57-
action = random_choice()
58-
else:
59-
action = self.select_action(state)
56+
action = self.select_action(state)
6057
self.prev_state = state
6158
self.prev_action = action
6259
return action
@@ -73,7 +70,8 @@ def select_action(self, state):
7370

7471
def find_state(self, opponent):
7572
"""
76-
Finds the my_state (the opponents last n moves + its previous proportion of playing C) as a hashable state
73+
Finds the my_state (the opponents last n moves +
74+
its previous proportion of playing C) as a hashable state
7775
"""
7876
prob = '{:.1f}'.format(opponent.cooperations)
7977
return ''.join(opponent.history[-self.memory_length:]) + prob

axelrod/tests/unit/test_apavlov.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ def test_strategy(self):
7575
attrs={"opponent_class": "STFT"})
7676
self.responses_test([C, D, D, D, C, C], [D, D, D, C, C, C], [C],
7777
attrs={"opponent_class": "STFT"})
78+
self.responses_test([C, D, D, D, C, C], [D, D, D, C, C, C], [C],
79+
attrs={"opponent_class": "STFT"})
80+
81+
# Specific case for STFT when responding with TFT
82+
opponent = axelrod.Player()
83+
player = axelrod.APavlov2006()
84+
player.history = [D] * 8
85+
opponent.history = [D] * 8
86+
player.opponent_class = "STFT"
87+
self.assertEqual(player.strategy(opponent), D)
88+
opponent.history.append(C)
89+
self.assertEqual(player.strategy(opponent), C)
90+
7891

7992
self.responses_test([C, C, C, C, C, D], [C, C, C, C, D, D], [D],
8093
attrs={"opponent_class": "Random"})

axelrod/tests/unit/test_axelrod_first.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def test_strategy(self):
7272
self.responses_test([C, C, C], [D, C, D], [C])
7373
self.responses_test([C, C, D, D], [C, D, D, D], [D])
7474
self.responses_test([C, C, C, C], [D, C, D, C], [C])
75+
self.responses_test([C, D, C, C, D, D], [C, C, C, C, D, D], [C])
7576

7677
def test_not_revised(self):
7778
# Test not revised

axelrod/tests/unit/test_deterministic_cache.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import sys
44
from axelrod import DeterministicCache, TitForTat, Defector, Random
55

6+
import pickle
7+
import tempfile
8+
69

710
class TestDeterministicCache(unittest.TestCase):
811

@@ -39,6 +42,17 @@ def test_setitem(self):
3942
cache[self.test_key] = self.test_value
4043
self.assertEqual(cache[self.test_key], self.test_value)
4144

45+
def test_setitem_invalid_key(self):
46+
cache = DeterministicCache()
47+
invalid_key = (1, 2, 3, 4)
48+
with self.assertRaises(ValueError):
49+
cache[invalid_key] = 3
50+
51+
def test_setitem_invalid_value(self):
52+
cache = DeterministicCache()
53+
with self.assertRaises(ValueError):
54+
cache[self.test_key] = 5
55+
4256
def test_set_immutable_cache(self):
4357
cache = DeterministicCache()
4458
cache.mutable = False
@@ -57,6 +71,7 @@ def test_is_valid_key(self):
5771
self.assertFalse(cache._is_valid_key(('test', 'test', 'test')))
5872
self.assertFalse(cache._is_valid_key((TitForTat, 'test', 2)))
5973
self.assertFalse(cache._is_valid_key(('test', TitForTat, 2)))
74+
self.assertFalse(cache._is_valid_key((TitForTat, TitForTat, TitForTat)))
6075
# Should return false if either player class is stochastic
6176
self.assertFalse(cache._is_valid_key((Random, TitForTat, 2)))
6277
self.assertFalse(cache._is_valid_key((TitForTat, Random, 2)))
@@ -79,3 +94,13 @@ def test_load(self):
7994
cache = DeterministicCache()
8095
cache.load(self.test_load_file)
8196
self.assertEqual(cache[self.test_key], self.test_value)
97+
98+
def test_load_error_for_inccorect_format(self):
99+
tmp_file = tempfile.NamedTemporaryFile()
100+
with open(tmp_file.name, 'wb') as io:
101+
pickle.dump(range(5), io)
102+
103+
with self.assertRaises(ValueError):
104+
cache = DeterministicCache()
105+
cache.load(tmp_file.name)
106+

0 commit comments

Comments
 (0)