Skip to content

Commit

Permalink
"watch": Support same file in multiple bundles.
Browse files Browse the repository at this point in the history
There are now also some tests for the watch command.

Closes miracle2k#127.
  • Loading branch information
miracle2k committed Apr 20, 2012
1 parent 6a324d5 commit 55c8326
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 5 deletions.
22 changes: 17 additions & 5 deletions src/webassets/script.py
Expand Up @@ -199,14 +199,22 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None,
if len(built):
self.event_handlers['post_build']()

def watch(self):
def watch(self, loop=None):
"""Watch assets for changes.
TODO: This should probably also restart when the code changes.
``loop``
A callback, taking no arguments, to be called once every loop
iteration. Can be useful to integrate the command with other code.
If not specified, the loop wil call ``time.sleep()``.
"""
# TODO: This should probably also restart when the code changes.
_mtimes = {}
_win = (sys.platform == "win32")
def check_for_changes():
# Do not update original mtimes dict right away, so that we detect
# all bundle changes if a file is in multiple bundles.
_new_mtimes = _mtimes.copy()

changed_bundles = []
for bundle in self.environment:
for filename in get_all_bundle_files(bundle):
Expand All @@ -217,9 +225,11 @@ def check_for_changes():

if _mtimes.get(filename, mtime) != mtime:
changed_bundles.append(bundle)
_mtimes[filename] = mtime
_new_mtimes[filename] = mtime
break
_mtimes[filename] = mtime
_new_mtimes[filename] = mtime

_mtimes.update(_new_mtimes)
return changed_bundles

try:
Expand All @@ -236,7 +246,9 @@ def check_for_changes():
print "Failed: %s" % e
if len(built):
self.event_handlers['post_build']()
time.sleep(0.1)
do_end = loop() if loop else time.sleep(0.1)
if do_end:
break
except KeyboardInterrupt:
pass

Expand Down
88 changes: 88 additions & 0 deletions tests/test_script.py
Expand Up @@ -6,8 +6,11 @@

from __future__ import with_statement

from os import path
import logging
from threading import Thread, Event
from nose.tools import assert_raises
import time
from webassets import Bundle
from webassets.script import main, CommandLineEnvironment, CommandError
from webassets.test import TempEnvironmentHelper
Expand Down Expand Up @@ -137,3 +140,88 @@ def test_manifest(self):
# Use prefix syntax
self.cmd_env.build(manifest='file:miau')
assert self.exists('media/sub/miau')


class TestWatchCommand(TestCLI):
"""This is a hard one to test.
We run the watch command in a thread, and rely on it's ``loop`` argument
to stop the thread again.
"""

default_files = {'in': 'foo', 'out': 'bar'}

def watch_loop(self):
# Hooked into the loop of the ``watch`` command.
# Allows stopping the thread.
self.has_looped.set()
time.sleep(0.01)
if getattr(self, 'stopped', False):
return True

def start_watching(self):
"""Run the watch command in a thread."""
self.has_looped = Event()
t = Thread(target=self.cmd_env.watch, kwargs={'loop': self.watch_loop})
t.daemon = True # In case something goes wrong with stopping, this
# will allow the test process to be end nonetheless.
t.start()
self.t = t
# Wait for first iteration, which will initialize the mtimes. Only
# after this will ``watch`` be able to detect changes.
self.has_looped.wait(1)

def stop_watching(self):
"""Stop the watch command thread."""
self.stopped = True
self.t.join(1)

def __enter__(self):
self.start_watching()

def __exit__(self, exc_type, exc_val, exc_tb):
self.stop_watching()

def test(self):
# Register a bundle to watch
bundle = self.mkbundle('in', output='out')
self.env.register('test', bundle)
now = self.setmtime('in', 'out')

# Assert initial state
assert self.get('out') == 'bar'

# While watch is running, change input mtime
with self:
self.setmtime('in', mtime=now+10)
# Allow watch to pick up the change
time.sleep(0.2)

# output file has been updated.
assert self.get('out') == 'foo'


def test_same_file_multiple_bundles(self):
"""[Bug] Test watch command can deal with the same file being part
of multiple bundles. This was not always the case (github-127).
"""
self.create_files({'out2': 'bar'})
bundle1 = self.mkbundle('in', output='out')
bundle2 = self.mkbundle('in', output='out2')
self.env.register('test1', bundle1)
self.env.register('test2', bundle2)
now = self.setmtime('in', 'out', 'out2')

# Assert initial state
assert self.get('out') == 'bar'
assert self.get('out2') == 'bar'

# While watch is running, change input mtime
with self:
self.setmtime('in', mtime=now+10)
# Allow watch to pick up the change
time.sleep(0.2)

# Both output files have been updated.
assert self.get('out') == 'foo'
assert self.get('out2') == 'foo'

0 comments on commit 55c8326

Please sign in to comment.