iTerm coprocess reporting result of (Mocha) tests run via nodemon
See this gist:
| #!/usr/bin/python | |
| # -*- coding: utf-8 -*- | |
| # Inspired by https://gist.github.com/ddbeck/1421861 | |
| # About: This coprocess produces an OSX notification with results of Mocha tests run via nodemon | |
| # # Installation: | |
| # 1. Make this script executable | |
| # 2. Register it: iTerm - Profiles - Open Profiles... - [Edit Profiles...] - <select your profile> - | |
| # - Advanced - Triggers - [Edit] - add a new trigger: | |
| # Regular Expression: [nodemon] starting `npm .*(run )?test.*` | |
| # Action: Run Coprocess | |
| # Parameters: /path/to/iterm-notify-test-failure.sh | |
| # 3. Troubleshooting tips: | |
| # * Verify the script can be run from the command line | |
| # * Instead waiting for Mocha to run, use e.g. `echo "npm test"` to trigger it | |
| # * Print into a file to produce debugging log or/and use e.g. the `say` osx command to track what is being processed | |
| import subprocess | |
| import sys | |
| import re | |
| ## Example input from a Mocha run: | |
| # 28 Apr 10:12:26 - [nodemon] starting `npm --silent run test:server` | |
| # ... | |
| # CacheItem fetching from a source | |
| # ✓ should return the in-progress-call promise if expired and requireNonExpired set | |
| # 1) should refresh its value even though valid when forceRefresh=true and return it | |
| # lookup | |
| # ✓ should return a promise | |
| # | |
| # | |
| # 2 passing (12ms) | |
| # 1 failing | |
| # | |
| # 1) CacheItem fetching from a source should refresh its value even though valid when forceRefresh=true and return it: | |
| # | |
| # AssertionError: expected 2 to deeply equal 23 | |
| # + expected - actual | |
| # ... | |
| # 28 Apr 10:12:36 - [nodemon] clean exit - waiting for changes before restart | |
| def incoming(): | |
| while True: | |
| yield raw_input() | |
| def notify(title_end = 'FAILED', msg = ''): | |
| subprocess.call([ | |
| 'osascript', | |
| "-e", | |
| ('display notification "%s" with title "Tests %s"' % | |
| (msg, title_end)) | |
| ]) | |
| def main(): | |
| title_end = 'FAILED: ' | |
| msg = '' | |
| failingCountPattern = re.compile('.*(\d+) failing') | |
| # Failed test name example: ' [2K[0G [31m 1) Stack should do something [0m' | |
| # (the [2k etc. are Bash color escape/sequences, the ' ' is actually the control char \033) | |
| failedTestPattern = re.compile('(\033\\[\w+| \\W)+(\\d+\\) .*)\033\\[\w+') # TODO The end color seq. is not ignored | |
| for line in incoming(): | |
| failingCountMatch = failingCountPattern.match(line) | |
| failedTestMatch = failedTestPattern.match(line) | |
| if failingCountMatch: # ex: " 1 failing" | |
| title_end += failingCountMatch.group(1) | |
| if failedTestPattern.match(line): | |
| msg += failedTestMatch.group(2) + ': ' # Notifications display only one line so just the 1st error is displayed | |
| if 'AssertionError' in line: | |
| msg += line + '\n' | |
| if '[nodemon] app crashed' in line: | |
| notify(title_end = title_end, msg = msg) | |
| sys.exit(0) | |
| if '[nodemon] clean exit' in line: | |
| notify("OK"); | |
| sys.exit(0) | |
| if __name__ == '__main__': | |
| main() |