Skip to content

Commit

Permalink
use pytest fixtures and remove unittest-style stuff
Browse files Browse the repository at this point in the history
A common set of Process subclasses and fixtures are now provided in
conftest.py
  • Loading branch information
benbovy committed Aug 1, 2017
1 parent f9d54bb commit cfb01d5
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 277 deletions.
150 changes: 150 additions & 0 deletions xsimlab/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
This module provides a set of Process subclasses and pytest fixtures that are
used across the tests.
"""
from textwrap import dedent

import pytest
import numpy as np

from xsimlab.variable.base import (Variable, ForeignVariable, VariableGroup,
VariableList, diagnostic)
from xsimlab.process import Process
from xsimlab.model import Model


class ExampleProcess(Process):
"""A full example of process interface.
"""
var = Variable((), provided=True)
var_list = VariableList([Variable('x'), Variable(((), 'x'))])
var_group = VariableGroup('group')
no_var = 'this is not a variable object'

class Meta:
time_dependent = False

@diagnostic
def diag(self):
return 1


@pytest.fixture
def process():
return ExampleProcess()


@pytest.fixture(scope='session')
def process_repr():
return dedent("""\
Variables:
* diag DiagnosticVariable
* var Variable ()
var_group VariableGroup 'group'
var_list VariableList
- Variable ('x')
- Variable (), ('x')
Meta:
time_dependent: False""")


class Grid(Process):
x_size = Variable((), optional=True, description='grid size')
x = Variable('x', provided=True)

class Meta:
time_dependent = False

def validate(self):
if self.x_size.value is None:
self.x_size.value = 5

def initialize(self):
self.x.value = np.arange(self.x_size.value)


class Quantity(Process):
quantity = Variable('x', description='a quantity')
all_effects = VariableGroup('effect')

def run_step(self, *args):
self.quantity.change = sum((var.value for var in self.all_effects))

def finalize_step(self):
self.quantity.state += self.quantity.change

@diagnostic
def some_derived_quantity(self):
"""some derived quantity."""
return 1

@diagnostic({'units': 'm'})
def other_derived_quantity(self):
"""other derived quantity."""
return 2


class SomeProcess(Process):
some_param = Variable((), description='some parameter')
copy_param = Variable((), provided=True)
x = ForeignVariable(Grid, 'x')
quantity = ForeignVariable(Quantity, 'quantity')
some_effect = Variable('x', group='effect', provided=True)

def initialize(self):
self.copy_param.value = self.some_param.value

def run_step(self, dt):
self.some_effect.value = self.x.value * self.some_param.value + dt

def finalize(self):
self.some_effect.rate = 0


class OtherProcess(Process):
x = ForeignVariable(Grid, 'x')
copy_param = ForeignVariable(SomeProcess, 'copy_param')
quantity = ForeignVariable(Quantity, 'quantity')
other_effect = Variable('x', group='effect', provided=True)

def run_step(self, dt):
self.other_effect.value = self.x.value * self.copy_param.value - dt

@diagnostic
def x2(self):
return self.x * 2


class PlugProcess(Process):
meta_param = Variable(())
some_param = ForeignVariable(SomeProcess, 'some_param', provided=True)

def run_step(self, *args):
self.some_param.value = self.meta_param.value


@pytest.fixture
def model():
model = Model({'grid': Grid,
'some_process': SomeProcess,
'other_process': OtherProcess,
'quantity': Quantity})

model.grid.x_size.value = 10
model.quantity.quantity.state = np.zeros(10)
model.some_process.some_param.value = 1

return model


@pytest.fixture(scope='session')
def model_repr():
return dedent("""\
<xsimlab.Model (4 processes, 3 inputs)>
grid
x_size (in) grid size
some_process
some_param (in) some parameter
other_process
quantity
quantity (in) a quantity""")
4 changes: 1 addition & 3 deletions xsimlab/tests/test_formatting.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import unittest

from xsimlab.formatting import pretty_print, maybe_truncate, wrap_indent


class TestFormatting(unittest.TestCase):
class TestFormatting(object):

def test_maybe_truncate(self):
assert maybe_truncate('test', 10) == 'test'
Expand Down
133 changes: 16 additions & 117 deletions xsimlab/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,17 @@
import unittest
from textwrap import dedent

import pytest
import numpy as np
from numpy.testing import assert_array_equal

from xsimlab.variable.base import (Variable, ForeignVariable, VariableGroup,
diagnostic)
from xsimlab.variable.base import Variable
from xsimlab.process import Process
from xsimlab.model import Model
from xsimlab.tests.conftest import (Grid, SomeProcess, OtherProcess, Quantity,
PlugProcess)


class Grid(Process):
x_size = Variable((), optional=True, description='grid size')
x = Variable('x', provided=True)

class Meta:
time_dependent = False

def validate(self):
if self.x_size.value is None:
self.x_size.value = 5

def initialize(self):
self.x.value = np.arange(self.x_size.value)


class Quantity(Process):
quantity = Variable('x', description='a quantity')
all_effects = VariableGroup('effect')

def run_step(self, *args):
self.quantity.change = sum((var.value for var in self.all_effects))

def finalize_step(self):
self.quantity.state += self.quantity.change


class SomeProcess(Process):
some_param = Variable((), description='some parameter')
copy_param = Variable((), provided=True)
x = ForeignVariable(Grid, 'x')
quantity = ForeignVariable(Quantity, 'quantity')
some_effect = Variable('x', group='effect', provided=True)

def initialize(self):
self.copy_param.value = self.some_param.value

def run_step(self, dt):
self.some_effect.value = self.x.value * self.some_param.value + dt

def finalize(self):
self.some_effect.rate = 0


class OtherProcess(Process):
x = ForeignVariable(Grid, 'x')
copy_param = ForeignVariable(SomeProcess, 'copy_param')
quantity = ForeignVariable(Quantity, 'quantity')
other_effect = Variable('x', group='effect', provided=True)

def run_step(self, dt):
self.other_effect.value = self.x.value * self.copy_param.value - dt

@diagnostic
def x2(self):
return self.x * 2


class PlugProcess(Process):
meta_param = Variable(())
some_param = ForeignVariable(SomeProcess, 'some_param', provided=True)

def run_step(self, *args):
self.some_param.value = self.meta_param.value


def get_test_model():
model = Model({'grid': Grid,
'some_process': SomeProcess,
'other_process': OtherProcess,
'quantity': Quantity})

model.grid.x_size.value = 10
model.quantity.quantity.state = np.zeros(10)
model.some_process.some_param.value = 1

return model


class TestModel(unittest.TestCase):
class TestModel(object):

def test_constructor(self):
def test_constructor(self, model):
# test invalid processes
with pytest.raises(TypeError):
Model({'not_a_class': Grid()})
Expand All @@ -108,23 +28,20 @@ class OtherClass(object):
assert "is not a subclass" in str(excinfo.value)

# test process ordering
model = get_test_model()
expected = ['grid', 'some_process', 'other_process', 'quantity']
assert list(model) == expected

# test dict-like vs. attribute access
assert model['grid'] is model.grid

def test_input_vars(self):
model = get_test_model()
def test_input_vars(self, model):
expected = {'grid': ['x_size'],
'some_process': ['some_param'],
'quantity': ['quantity']}
actual = {k: list(v.keys()) for k, v in model.input_vars.items()}
assert expected == actual

def test_is_input(self):
model = get_test_model()
def test_is_input(self, model):
assert model.is_input(model.grid.x_size) is True
assert model.is_input(('grid', 'x_size')) is True
assert model.is_input(('quantity', 'all_effects')) is False
Expand All @@ -133,44 +50,38 @@ def test_is_input(self):
external_variable = Variable(())
assert model.is_input(external_variable) is False

def test_initialize(self):
model = get_test_model()
def test_initialize(self, model):
model.initialize()
expected = np.arange(10)
assert_array_equal(model.grid.x.value, expected)

def test_run_step(self):
model = get_test_model()
def test_run_step(self, model):
model.initialize()
model.run_step(100)

expected = model.grid.x.value * 2
assert_array_equal(model.quantity.quantity.change, expected)

def test_finalize_step(self):
model = get_test_model()
def test_finalize_step(self, model):
model.initialize()
model.run_step(100)
model.finalize_step()

expected = model.grid.x.value * 2
assert_array_equal(model.quantity.quantity.state, expected)

def test_finalize(self):
model = get_test_model()
def test_finalize(self, model):
model.finalize()
assert model.some_process.some_effect.rate == 0

def test_clone(self):
model = get_test_model()
def test_clone(self, model):
cloned = model.clone()

for (ck, cp), (k, p) in zip(cloned.items(), model.items()):
assert ck == k
assert cp is not p

def test_update_processes(self):
model = get_test_model()
def test_update_processes(self, model):
expected = Model({'grid': Grid,
'plug_process': PlugProcess,
'some_process': SomeProcess,
Expand All @@ -181,8 +92,7 @@ def test_update_processes(self):
# TODO: more advanced (public?) test function to compare two models?
assert list(actual) == list(expected)

def test_drop_processes(self):
model = get_test_model()
def test_drop_processes(self, model):

expected = Model({'grid': Grid,
'some_process': SomeProcess,
Expand All @@ -195,16 +105,5 @@ def test_drop_processes(self):
actual = model.drop_processes(['some_process', 'other_process'])
assert list(actual) == list(expected)

def test_repr(self):
model = get_test_model()
expected = dedent("""\
<xsimlab.Model (4 processes, 3 inputs)>
grid
x_size (in) grid size
some_process
some_param (in) some parameter
other_process
quantity
quantity (in) a quantity""")

assert repr(model) == expected
def test_repr(self, model, model_repr):
assert repr(model) == model_repr

0 comments on commit cfb01d5

Please sign in to comment.