diff --git a/sbws/util/stem.py b/sbws/util/stem.py
index 48a6da75506beffabe37dc10e89f87da8c795b3f..5291bbade8d2b73f3188de8dfc60fa6c980d07c0 100644
--- a/sbws/util/stem.py
+++ b/sbws/util/stem.py
@@ -83,6 +83,18 @@ def init_controller(port=None, path=None, set_custom_stream_settings=True):
     return c, ''
 
 
+def is_bootstrapped(c):
+    if not is_controller_okay(c):
+        return False
+    line = c.get_info('status/bootstrap-phase')
+    state, _, progress, *_ = line.split()
+    progress = int(progress.split('=')[1])
+    if state == 'NOTICE' and progress == 100:
+        return True
+    log.debug('Not bootstrapped. state={} progress={}'.format(state, progress))
+    return False
+
+
 def is_controller_okay(c):
     if not c:
         return False
diff --git a/tests/integration/__touch__.py b/tests/integration/__touch__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..a64e16508c4134961f331b1cebc67fa9867f95fe
--- /dev/null
+++ b/tests/integration/conftest.py
@@ -0,0 +1,42 @@
+import pytest
+from tempfile import TemporaryDirectory
+from sbws.util.parser import create_parser
+from sbws.util.config import get_config
+from sbws.util.stem import launch_tor
+import sbws.core.init
+
+
+@pytest.fixture(scope='session')
+def parser():
+    return create_parser()
+
+
+@pytest.fixture(scope='session')
+def persistent_empty_dotsbws(parser):
+    '''
+    Creates a ~/.sbws with nothing in it but a config.ini
+    '''
+    d = TemporaryDirectory()
+    args = parser.parse_args(
+        '-d {} --log-level DEBUG init'.format(d.name).split())
+    conf = get_config(args)
+    sbws.core.init.main(args, conf)
+    return d
+
+
+@pytest.fixture(scope='session')
+def persistent_launch_tor(parser, persistent_empty_dotsbws):
+    d = persistent_empty_dotsbws
+    args = parser.parse_args('-d {}'.format(d.name).split())
+    conf = get_config(args)
+    conf['tor']['extra_lines'] = '''
+DirAuthority auth1 orport=2002 no-v2 v3ident=D7DBC517EFD2BA1A5012CF1BD0BB38F17C8160BD 127.10.0.1:2003 AA45C13025C037F056E734169891878ED0880231
+DirAuthority auth2 orport=2002 no-v2 v3ident=4EE103A081F400E6622F5461D51782B876BB5C24 127.10.0.2:2003 E7B3C9A0040D628DAC88B0251AE6334D28E8F531
+DirAuthority auth3 orport=2002 no-v2 v3ident=8B85069C7FC0593801E6491A34100264FCE28980 127.10.0.3:2003 35E3B8BB71C81355649AEC5862ECB7ED7EFDBC5C
+TestingTorNetwork 1
+NumCPUs 1
+LogTimeGranularity 1
+SafeLogging 0
+'''
+    cont = launch_tor(conf)
+    return cont
diff --git a/tests/integration/test_stem.py b/tests/integration/test_stem.py
new file mode 100644
index 0000000000000000000000000000000000000000..c36879df59c80cbf81375941d78ea25c55b4a2d9
--- /dev/null
+++ b/tests/integration/test_stem.py
@@ -0,0 +1,27 @@
+import sbws.util.stem as stem_utils
+from stem.descriptor.router_status_entry import RouterStatusEntryV3
+
+
+def test_foo(persistent_launch_tor):
+    cont = persistent_launch_tor
+    assert stem_utils.is_controller_okay(cont)
+    assert stem_utils.is_bootstrapped(cont)
+
+
+def test_get_relay_from_fp(persistent_launch_tor):
+    cont = persistent_launch_tor
+    # AA45C13025C037F056E734169891878ED0880231 is auth1
+    relay = stem_utils.fp_or_nick_to_relay(
+        cont, 'AA45C13025C037F056E734169891878ED0880231')
+    assert isinstance(relay, RouterStatusEntryV3)
+    assert relay.fingerprint == 'AA45C13025C037F056E734169891878ED0880231'
+    assert relay.nickname == 'auth1'
+
+
+def test_get_relay_from_nick(persistent_launch_tor):
+    cont = persistent_launch_tor
+    # AA45C13025C037F056E734169891878ED0880231 is auth1
+    relay = stem_utils.fp_or_nick_to_relay(cont, 'auth1')
+    assert isinstance(relay, RouterStatusEntryV3)
+    assert relay.fingerprint == 'AA45C13025C037F056E734169891878ED0880231'
+    assert relay.nickname == 'auth1'
diff --git a/tests/testnets/reproducible.tar b/tests/testnets/reproducible.tar
new file mode 100644
index 0000000000000000000000000000000000000000..4f6be8893987e836dd925e19a85e7a091fd5567b
Binary files /dev/null and b/tests/testnets/reproducible.tar differ
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/conftest.py b/tests/unit/conftest.py
similarity index 100%
rename from tests/conftest.py
rename to tests/unit/conftest.py
diff --git a/tests/core/test_cleanup.py b/tests/unit/core/test_cleanup.py
similarity index 99%
rename from tests/core/test_cleanup.py
rename to tests/unit/core/test_cleanup.py
index 8f35e9ada72259caa75916de8b569bfdf0ca453e..0f6fc4132a298368501ad4e5bc7a1d5cc6456181 100644
--- a/tests/core/test_cleanup.py
+++ b/tests/unit/core/test_cleanup.py
@@ -1,7 +1,7 @@
 from sbws.util.config import get_config
 from sbws.globals import touch_file
 import sbws.core.cleanup
-from tests.globals import monotonic_time
+from tests.unit.globals import monotonic_time
 from unittest.mock import patch
 import logging
 import os
diff --git a/tests/core/test_generate.py b/tests/unit/core/test_generate.py
similarity index 100%
rename from tests/core/test_generate.py
rename to tests/unit/core/test_generate.py
diff --git a/tests/core/test_stats.py b/tests/unit/core/test_stats.py
similarity index 99%
rename from tests/core/test_stats.py
rename to tests/unit/core/test_stats.py
index 43c1e2772cdfbf979699d7678fff5e9a826ba1af..d54f83b0c6ebf1dc8e9d254fea8cc85093064ed6 100644
--- a/tests/core/test_stats.py
+++ b/tests/unit/core/test_stats.py
@@ -6,7 +6,7 @@ from sbws.lib.resultdump import Result
 from sbws.lib.resultdump import write_result_to_datadir
 import sbws.core.init
 import sbws.core.stats
-from tests.globals import monotonic_time
+from tests.unit.globals import monotonic_time
 from unittest.mock import patch
 from datetime import datetime
 import os
diff --git a/tests/globals.py b/tests/unit/globals.py
similarity index 100%
rename from tests/globals.py
rename to tests/unit/globals.py
diff --git a/tests/lib/test_results.py b/tests/unit/lib/test_results.py
similarity index 99%
rename from tests/lib/test_results.py
rename to tests/unit/lib/test_results.py
index 95a356dfe513e89376429905898fc3aa2632e438..449222e8f23cc573efae9f9c41b6e19d56d761b8 100644
--- a/tests/lib/test_results.py
+++ b/tests/unit/lib/test_results.py
@@ -7,7 +7,7 @@ from sbws.lib.resultdump import ResultErrorAuth
 from sbws.lib.resultdump import ResultErrorCircuit
 from sbws.lib.resultdump import ResultErrorStream
 from sbws.lib.resultdump import _ResultType
-from tests.globals import monotonic_time
+from tests.unit.globals import monotonic_time
 
 
 @patch('time.time')
diff --git a/tests/lib/test_v3bwfile.py b/tests/unit/lib/test_v3bwfile.py
similarity index 100%
rename from tests/lib/test_v3bwfile.py
rename to tests/unit/lib/test_v3bwfile.py
diff --git a/tests/util/test_config.py b/tests/unit/util/test_config.py
similarity index 100%
rename from tests/util/test_config.py
rename to tests/unit/util/test_config.py
diff --git a/tests/util/test_userquery.py b/tests/unit/util/test_userquery.py
similarity index 100%
rename from tests/util/test_userquery.py
rename to tests/unit/util/test_userquery.py
diff --git a/tox.ini b/tox.ini
index fba0d6568b866f5899d65f49beaa7885aaf6bf1a..65495b0a67a912f15e698abc2bd8e0710d71b0a5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,12 @@
 [tox]
 skip_missing_interpreters = True
-envlist = clean, lint, py34, py35, py36, stats
+envlist = clean, lint, py34, py35, py36, stats, integration
 
 [travis]
 python =
-  3.4: lint, py34
+  3.4: lint, py34, integration
+  3.5: py35, integration
+  3.6: py36, integration
 
 [testenv:clean]
 skip_install = True
@@ -14,6 +16,24 @@ deps =
 commands=
    coverage erase
 
+[testenv:integration]
+ignore_errors = True
+deps = .[test]
+whitelist_externals =
+    rm
+    tar
+    time
+    bash
+    sleep
+changedir = {toxinidir}/tests/testnets
+commands =
+    rm -vrf reproducible
+    tar vxf reproducible.tar
+    bash ./reproducible/start.sh
+    time bash -c "python3 ./reproducible/wait.py ./reproducible/{auth,relay,exit}*"
+    coverage run --rcfile={toxinidir}/.coveragerc --source=sbws -m pytest -s {toxinidir}/tests/integration -vv
+    bash ./reproducible/stop.sh
+
 [testenv:lint]
 skip_install = True
 deps = .[dev]
@@ -36,7 +56,7 @@ install_command =
    pip install --process-dependency-links {opts} {packages}
 deps = .[test]
 commands =
-   coverage run --rcfile={toxinidir}/.coveragerc --source=sbws -m pytest -s {toxinidir}/tests -vv
+   coverage run --rcfile={toxinidir}/.coveragerc --source=sbws -m pytest -s {toxinidir}/tests/unit -vv
 passenv =
   TRAVIS
   TRAVIS_JOB_ID