Skip to content

Commit 2810fbd

Browse files
committed
- Support for running a before_script: before starting tmux session.
- Separate run_before_script into its wrapper with custom exception. - Create temp_session context manager. - Kill session if before_script fails. Handle case with temp_session context mgr where session is killed within its scope. - Update CHANGES. - Add some docs for new before_script command
1 parent 1038970 commit 2810fbd

File tree

8 files changed

+314
-8
lines changed

8 files changed

+314
-8
lines changed

CHANGES

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Here you can find the recent changes to tmuxp.
77
CURRENT
88
-------
99

10+
- [tests]: New context manager for tests, ``temp_session``.
11+
- [tests]: New testsuite, ``testsuite.test_utils`` for testing testsuite
12+
tools.
13+
- [config] [builder]: New command, ``before_script``, which is a file to
14+
be executed with a return code. It can be a bash, perl, python etc.
15+
script.
1016
- [docs]: :ref:`python_api_quickstart` per `Issue #56`_.
1117

1218
.. _Issue #56: https://github.com/tony/tmuxp/issues/56

doc/examples.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,54 @@ JSON
246246
.. literalinclude:: ../.tmuxp.json
247247
:language: json
248248

249+
Run script before launch
250+
------------------------
251+
252+
You can use ``before_script`` to run a script before the tmux session
253+
starts building.
254+
255+
It works by using the `Exit Status`_ code returned by a script. Your
256+
script can be any type, including bash, python, ruby, etc.
257+
258+
A successful script will exit with a status of ``0``.
259+
260+
You can use this for things like bootstrapping ruby / python environments
261+
for a project (or checking to verify their installation).
262+
263+
Run a python script (and check for it's return code), the script is
264+
*relative to the ``.tmuxp.yaml``'s root* (Windows and panes omitted in
265+
this example):
266+
267+
.. code-block:: yaml
268+
269+
session_name: my session
270+
before_script: bootstrap.py
271+
# ... the rest of your config
272+
273+
.. code-block:: json
274+
275+
{
276+
"session_name": "my session",
277+
"before_script": "bootstrap.py"
278+
}
279+
280+
Run a shell script + check for return code on an absolute path. (Windows
281+
and panes omitted in this example)
282+
283+
.. code-block:: yaml
284+
session_name: another example
285+
before_script: /absolute/path/this.sh # abs path to shell script
286+
# ... the rest of your config
287+
288+
.. code-block:: json
289+
290+
{
291+
"session_name": "my session",
292+
"before_script": "/absolute/path/this.sh"
293+
}
294+
295+
.. _Exit Status: http://tldp.org/LDP/abs/html/exit-status.html
296+
249297
Project configs
250298
---------------
251299

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
echo hello
4+
echo $? # Exit status 0 returned because command executed successfully.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
exit 113 # Will return 113 to shell.
4+
# To verify this, type "echo $?" after script terminates.

tmuxp/testsuite/helpers.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
with_statement, unicode_literals
1111

1212
import time
13-
from random import randint
1413
import logging
14+
import contextlib
15+
1516
try:
1617
import unittest2 as unittest
1718
except ImportError: # Python 2.7
1819
import unittest
1920

21+
from random import randint
22+
2023
from . import t
2124
from .. import Server, log, exc
2225

@@ -25,6 +28,28 @@
2528
TEST_SESSION_PREFIX = 'tmuxp_'
2629

2730

31+
def get_test_session_name(server, prefix='tmuxp_'):
32+
while True:
33+
session_name = prefix + str(randint(0, 9999999))
34+
if not t.has_session(session_name):
35+
break
36+
return session_name
37+
38+
39+
@contextlib.contextmanager
40+
def temp_session(server, session_name=None):
41+
if not session_name:
42+
session_name = get_test_session_name(server)
43+
44+
session = server.new_session(session_name)
45+
try:
46+
yield session
47+
finally:
48+
if server.has_session(session_name):
49+
session.kill_session()
50+
return
51+
52+
2853
class TestCase(unittest.TestCase):
2954

3055
"""Base TestClass so we don't have to try: unittest2 every module. """
@@ -80,10 +105,7 @@ def bootstrap(self):
80105
)
81106
]
82107

83-
while True:
84-
TEST_SESSION_NAME = TEST_SESSION_PREFIX + str(randint(0, 9999999))
85-
if not t.has_session(TEST_SESSION_NAME):
86-
break
108+
TEST_SESSION_NAME = get_test_session_name(server=t)
87109

88110
try:
89111
session = t.new_session(
@@ -113,4 +135,5 @@ def bootstrap(self):
113135
assert TEST_SESSION_NAME != 'tmuxp'
114136

115137
self.TEST_SESSION_NAME = TEST_SESSION_NAME
138+
self.server = t
116139
self.session = session

tmuxp/testsuite/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
"""Tests for tmuxp testsuite's helper and utility functions."""
3+
4+
from __future__ import absolute_import, division, print_function, \
5+
with_statement, unicode_literals
6+
7+
from .helpers import get_test_session_name, temp_session, TestCase, \
8+
TmuxTestCase, unittest
9+
10+
11+
class TempSession(TmuxTestCase):
12+
13+
def test_kills_session(self):
14+
server = self.server
15+
session_name = get_test_session_name(server=server)
16+
17+
with temp_session(server=server, session_name=session_name) as session:
18+
result = server.has_session(session_name)
19+
self.assertTrue(result)
20+
21+
self.assertFalse(server.has_session(session_name))
22+
23+
def test_if_session_killed_before(self):
24+
"""Handles situation where session already closed within context"""
25+
26+
server = self.server
27+
session_name = get_test_session_name(server=server)
28+
29+
with temp_session(server=server, session_name=session_name) as session:
30+
31+
# an error or an exception within a temp_session kills the session
32+
server.kill_session(session_name)
33+
34+
result = server.has_session(session_name)
35+
self.assertFalse(result)
36+
37+
# really dead?
38+
self.assertFalse(server.has_session(session_name))
39+
40+
41+
def suite():
42+
suite = unittest.TestSuite()
43+
suite.addTest(unittest.makeSuite(TempSession))
44+
return suite

tmuxp/testsuite/workspacebuilder.py

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@
1313
import sys
1414
import logging
1515
import unittest
16+
import subprocess
1617
import time
1718

1819
import kaptan
1920

2021
from .. import Window, config, exc
2122
from .._compat import text_type
2223
from ..workspacebuilder import WorkspaceBuilder
23-
from .helpers import TmuxTestCase
24+
from .helpers import TestCase, TmuxTestCase, temp_session
2425

2526
logger = logging.getLogger(__name__)
2627

2728
current_dir = os.path.abspath(os.path.dirname(__file__))
2829
example_dir = os.path.abspath(os.path.join(current_dir, '..', '..', 'examples'))
30+
fixtures_dir = os.path.abspath(os.path.join(current_dir, 'fixtures'))
2931

3032

3133
class TwoPaneTest(TmuxTestCase):
@@ -339,7 +341,7 @@ def test_blank_pane_count(self):
339341
test_config = kaptan.Kaptan().import_config(self.yaml_config_file).get()
340342
test_config = config.expand(test_config)
341343
# for window in test_config['windows']:
342-
# window['layout'] = 'tiled'
344+
# window['layout'] = 'tiled'
343345
builder = WorkspaceBuilder(sconf=test_config)
344346
builder.build(session=self.session)
345347

@@ -527,8 +529,133 @@ def test_window_index(self):
527529
self.assertEqual(int(window['window_index']), expected_index)
528530

529531

532+
class BeforeLoadScript(TmuxTestCase):
533+
534+
config_script_not_exists = """
535+
session_name: sampleconfig
536+
before_script: {fixtures_dir}/script_not_exists.sh
537+
windows:
538+
- panes:
539+
- pane
540+
"""
541+
542+
config_script_fails = """
543+
session_name: sampleconfig
544+
before_script: {fixtures_dir}/script_failed.sh
545+
windows:
546+
- panes:
547+
- pane
548+
"""
549+
550+
config_script_completes = """
551+
session_name: sampleconfig
552+
before_script: {fixtures_dir}/script_complete.sh
553+
windows:
554+
- panes:
555+
- pane
556+
"""
557+
558+
def test_throw_error_if_retcode_false(self):
559+
560+
sconfig = kaptan.Kaptan(handler='yaml')
561+
yaml = self.config_script_fails.format(
562+
fixtures_dir=fixtures_dir
563+
)
564+
sconfig = sconfig.import_config(yaml).get()
565+
sconfig = config.expand(sconfig)
566+
sconfig = config.trickle(sconfig)
567+
568+
builder = WorkspaceBuilder(sconf=sconfig)
569+
570+
with temp_session(self.server) as sess:
571+
session_name = sess.get('session_name')
572+
573+
with self.assertRaises(subprocess.CalledProcessError):
574+
builder.build(session=sess)
575+
576+
result = self.server.has_session(session_name)
577+
self.assertFalse(
578+
result,
579+
msg="Kills session if before_script exits with errcode"
580+
)
581+
582+
def test_throw_error_if_file_not_exists(self):
583+
584+
sconfig = kaptan.Kaptan(handler='yaml')
585+
yaml = self.config_script_not_exists.format(
586+
fixtures_dir=fixtures_dir
587+
)
588+
sconfig = sconfig.import_config(yaml).get()
589+
sconfig = config.expand(sconfig)
590+
sconfig = config.trickle(sconfig)
591+
592+
builder = WorkspaceBuilder(sconf=sconfig)
593+
594+
with temp_session(self.server) as sess:
595+
session_name = sess.get('session_name')
596+
temp_session_exists = self.server.has_session(sess.get('session_name'))
597+
self.assertTrue(temp_session_exists)
598+
with self.assertRaisesRegexp(
599+
(BeforeLoadScriptNotExists, OSError),
600+
'No such file or directory'
601+
):
602+
builder.build(session=sess)
603+
result = self.server.has_session(session_name)
604+
self.assertFalse(
605+
result,
606+
msg="Kills session if before_script doesn't exist"
607+
)
608+
609+
def test_true_if_test_passes(self):
610+
611+
sconfig = kaptan.Kaptan(handler='yaml')
612+
yaml = self.config_script_completes.format(
613+
fixtures_dir=fixtures_dir
614+
)
615+
sconfig = sconfig.import_config(yaml).get()
616+
sconfig = config.expand(sconfig)
617+
sconfig = config.trickle(sconfig)
618+
619+
builder = WorkspaceBuilder(sconf=sconfig)
620+
621+
with temp_session(self.session.server) as session:
622+
builder.build(session=self.session)
623+
624+
625+
from ..workspacebuilder import run_before_script, BeforeLoadScriptNotExists, \
626+
BeforeLoadScriptFailed
627+
628+
629+
class RunBeforeScript(TestCase):
630+
631+
def test_raise_BeforeLoadScriptNotExists_if_not_exists(self):
632+
script_file = os.path.join(fixtures_dir, 'script_noexists.sh')
633+
634+
with self.assertRaises(BeforeLoadScriptNotExists):
635+
run_before_script(script_file)
636+
637+
with self.assertRaises(OSError):
638+
run_before_script(script_file)
639+
640+
def test_raise_BeforeLoadScriptFailed_if_retcode(self):
641+
script_file = os.path.join(fixtures_dir, 'script_failed.sh')
642+
643+
with self.assertRaises(BeforeLoadScriptFailed):
644+
run_before_script(script_file)
645+
646+
with self.assertRaises(subprocess.CalledProcessError):
647+
run_before_script(script_file)
648+
649+
def test_return_stdout_if_exits_zero(self):
650+
script_file = os.path.join(fixtures_dir, 'script_complete.sh')
651+
652+
run_before_script(script_file)
653+
654+
530655
def suite():
531656
suite = unittest.TestSuite()
657+
suite.addTest(unittest.makeSuite(BeforeLoadScript))
658+
suite.addTest(unittest.makeSuite(RunBeforeScript))
532659
suite.addTest(unittest.makeSuite(BlankPaneTest))
533660
suite.addTest(unittest.makeSuite(FocusAndPaneIndexTest))
534661
suite.addTest(unittest.makeSuite(PaneOrderingTest))

0 commit comments

Comments
 (0)