diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa8607c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.swc_submission_id +*.pyc \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c39bb13 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" + +script: + - python swc-main.py -n diff --git a/README.md b/README.md index 35ff0d2..1ac2984 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ For learners ============ -This directory contains scripts for testing your machine to make sure +This directory contains script for testing your machine to make sure you have the software you'll need for your workshop installed. See the comments at the head of each script for more details, but you'll basically want to see something like: - $ python swc-installation-test-1.py + $ python swc-main.py + Checking for python version... Passed - $ python swc-installation-test-2.py - check virtual-shell... pass - … + check command line shell (virtual-shell)... pass + ... Successes: virtual-shell Bourne Again Shell (bash) 4.2.37 @@ -18,7 +18,9 @@ basically want to see something like: If you see something like: - $ python swc-installation-test-2.py + $ python swc-main.py + Checking for python version... + Passed check virtual-shell... fail … check for command line shell (virtual-shell) failed: @@ -36,7 +38,7 @@ follow the suggestions to try and install any missing software. For additional troubleshooting information, you can use the `--verbose` option: - $ python swc-installation-test-2.py --verbose + $ python swc-main.py --verbose check virtual-shell... fail … ================== @@ -48,17 +50,17 @@ option: For instructors =============== -`swc-installation-test-1.py` is pretty simple, and just checks that -the students have a recent enough version of Python installed that -they'll be able to parse `swc-installation-test-2.py`. The latter -checks for a list of dependencies and prints error messages if a +`swc-main.py` first checks that the students have a recent enough +version of Python installed that they'll be able to parse +`swc-main.py` completely. If python version is found to be at least 2.6, +then it checks for a list of dependencies and prints error messages if a package is not installed, or if the installed version is not current enough. By default, the script checks for pretty much anything that has ever been used at a Software Carpentry workshop, which is probably not what you want for your particular workshop. Before your workshop, you should go through -`swc-installation-test-2.py` and comment any dependencies you don't +`swc-main.py` and comment any dependencies you don't need out of the `CHECKS` list. You might also want to skim through the minimum version numbers listed where particular dependencies are defined (e.g. `('git', 'Git', (1, 7, 0), None)`). For the most part, diff --git a/api.py b/api.py new file mode 100644 index 0000000..f4da448 --- /dev/null +++ b/api.py @@ -0,0 +1,100 @@ +""" +This file sends the data collected by the script 'swc-main.py' +to server. +""" + +import json +import sys +import datetime +import platform as _platform + +try: + import httplib as http_client +except ImportError: + import http.client as http_client + + +def submit(successes_list, failures_list, HOST): + """ + This function sends the details of failures and successes to server. + """ + + endpoint = "/installation_data/" + + try: + with open('.swc_submission_id', 'r') as f: + first_line = f.readline() + unique_id = first_line.split("[key:]")[1] + date = first_line.split("[key:]")[0] + if date != str(datetime.date.today()): + unique_id = None + except: + unique_id = None + + successful_installs = [] + failed_installs = failures_list + for checker, version in successes_list: + successful_installs.append( + { + "name": checker.full_name(), + "version": version + } + ) + + user_system_info = { + "distribution_name": _platform.linux_distribution()[0], + "distribution_version": _platform.linux_distribution()[1], + "system": _platform.system(), + "system_version": _platform.version(), + "machine": _platform.machine(), + "system_platform": _platform.platform(), + "python_version": _platform.python_version() + } + + headers = {"Content-Type": "application/json"} + data = { + "successful_installs": successful_installs, + "failed_installs": failed_installs, + "user_system_info": user_system_info, + "unique_user_id": unique_id + } + + def senddata(): + final_data = json.dumps(data) + conn = http_client.HTTPConnection(HOST) + print("\nPushing the data to server....\n") + try: + conn.request("POST", endpoint, final_data, headers=headers) + response = conn.getresponse() + response_string = response.read() + if response.status == 200: + print("\nSuccessfully Pushed to Server!") + response = json.loads(response_string.decode('utf-8')) + unique_id = response.get("key") + file = open('.swc_submission_id', 'w+') + file.write(str(datetime.date.today()) + "[key:]" + unique_id) + else: + print("\nSomething bad happened at Server!") + except: + print("\nConnection could not be established with server!") + conn.close() + + global input + try: + input = raw_input # making it compatible for Python 3.x and 2.x + except NameError: + pass + choice = input("\nTo improve our lessons, we gather anonymous data about failed package installations." + " Can we send anonymous list of your packages? (y/N): ") + if choice == 'y' or choice == 'Y': + workshop_id = input("Please enter your workshop name (similar to '2016-08-13-place', ask your instructor for details) (none by default): ") + if not workshop_id: + workshop_id = None + email = input("What is your email address (none by default): ") + if not email: + email = None + data['user_system_info']['email_id'] = email + data['user_system_info']['workshop_id'] = workshop_id + senddata() + else: + return diff --git a/swc-installation-test-2.py b/requirements_check.py old mode 100755 new mode 100644 similarity index 90% rename from swc-installation-test-2.py rename to requirements_check.py index bd7960d..f054d58 --- a/swc-installation-test-2.py +++ b/requirements_check.py @@ -1,45 +1,14 @@ #!/usr/bin/env python -"""Test script to check for required functionality. - -Execute this code at the command line by typing: - - python swc-installation-test-2.py - -Run the script and follow the instructions it prints at the end. - -This script requires at least Python 2.6. You can check the version -of Python that you have installed with 'swc-installation-test-1.py'. - -By default, this script will test for all the dependencies your -instructor thinks you need. If you want to test for a different set -of packages, you can list them on the command line. For example: - - python swc-installation-test-2.py git virtual-editor - -This is useful if the original test told you to install a more recent -version of a particular dependency, and you just want to re-test that -dependency. """ +Some details about the implementation: -# Some details about the implementation: - -# The dependencies are divided into a hierarchy of classes rooted on -# Dependency class. You can refer to the code to see which package -# comes under which type of dependency. - -# The CHECKER dictionary stores information about all the dependencies -# and CHECKS stores list of the dependencies which are to be checked in -# the current workshop. - -# In the "__name__ == '__main__'" block, we launch all the checks with -# check() function, which prints information about the tests as they run -# and details about the failures after the tests complete. In case of -# failure, the functions print_system_info() and print_suggestions() -# are called after this, where the former prints information about the -# user's system for debugging purposes while the latter prints some -# suggestions to follow. +The dependencies are divided into a hierarchy of classes rooted on +Dependency class. You can refer to the code to see which package +comes under which type of dependency. +The CHECKER dictionary stores information about all the dependencies +""" from __future__ import print_function # for Python 2.6 compatibility @@ -79,47 +48,7 @@ def import_module(name): __version__ = '0.1' - -# Comment out any entries you don't need -CHECKS = [ -# Shell - 'virtual-shell', -# Editors - 'virtual-editor', -# Browsers - 'virtual-browser', -# Version control - 'git', - 'hg', # Command line tool - #'mercurial', # Python package - 'EasyMercurial', -# Build tools and packaging - 'make', - 'virtual-pypi-installer', - 'setuptools', - #'xcode', -# Testing - 'nosetests', # Command line tool - 'nose', # Python package - 'py.test', # Command line tool - 'pytest', # Python package -# SQL - 'sqlite3', # Command line tool - 'sqlite3-python', # Python package -# Python - 'python', - 'ipython', # Command line tool - 'IPython', # Python package - 'argparse', # Useful for utility scripts - 'numpy', - 'scipy', - 'matplotlib', - 'pandas', - #'sympy', - #'Cython', - #'networkx', - #'mayavi.mlab', - ] +# HOST = "127.0.0.1:5000" CHECKER = {} @@ -232,6 +161,10 @@ def get_url(self): return url return self._default_url + def get_data(self): + data = {"error_description" : self.message} + return data + def __str__(self): url = self.get_url() lines = [ @@ -250,8 +183,9 @@ def __str__(self): def check(checks=None): successes = [] failures = [] - if not checks: - checks = CHECKS + failure_messages = [] + # if not checks: + # checks = CHECKS for check in checks: try: checker = CHECKER[check] @@ -261,8 +195,16 @@ def check(checks=None): try: version = checker.check() except DependencyError as e: - failures.append(e) + if 'version' not in locals() or version is None: + version = "unknown" + failure_messages.append(e) _sys.stdout.write('fail\n') + e_data = e.get_data() + failures.append({ + "name": checker.full_name(), + "version" : version, + "error_description": e_data.get('error_description') + }) else: _sys.stdout.write('pass\n') successes.append((checker, version)) @@ -274,14 +216,12 @@ def check(checks=None): version or 'unknown')) if failures: print('\nFailures:') - printed = [] - for failure in failures: - if failure not in printed: - print() - print(failure) - printed.append(failure) - return False - return True + failure_messages = set(failure_messages) + for failure in failure_messages: + print() + print(failure) + return (False, successes, failures) + return (True, successes, failures) class Dependency (object): @@ -1009,34 +949,3 @@ def print_suggestions(instructor_fallback=True): print('') print('For help, email the *entire* output of this script to') print('your instructor.') - - -if __name__ == '__main__': - import optparse as _optparse - - parser = _optparse.OptionParser(usage='%prog [options] [check...]') - epilog = __doc__ - parser.format_epilog = lambda formatter: '\n' + epilog - parser.add_option( - '-v', '--verbose', action='store_true', - help=('print additional information to help troubleshoot ' - 'installation issues')) - options,args = parser.parse_args() - try: - passed = check(args) - except InvalidCheck as e: - print("I don't know how to check for {0!r}".format(e.check)) - print('I do know how to check for:') - for key,checker in sorted(CHECKER.items()): - if checker.long_name != checker.name: - print(' {0} {1}({2})'.format( - key, ' '*(20-len(key)), checker.long_name)) - else: - print(' {0}'.format(key)) - _sys.exit(1) - if not passed: - if options.verbose: - print() - print_system_info() - print_suggestions(instructor_fallback=True) - _sys.exit(1) diff --git a/swc-installation-test-1.py b/swc-installation-test-1.py deleted file mode 100755 index be027f9..0000000 --- a/swc-installation-test-1.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -"""Test script to check required Python version. - -Execute this code at the command line by typing: - - python swc-installation-test-1.py - -How to get a command line: - -- On OSX run this with the Terminal application. - -- On Windows, go to the Start menu, select 'Run' and type 'cmd' -(without the quotes) to run the 'cmd.exe' Windows Command Prompt. - -- On Linux, either use your login shell directly, or run one of a - number of graphical terminals (e.g. 'xterm', 'gnome-terminal', ...). - -For some screen shots, see: - - http://software-carpentry.org/setup/terminal.html - -Run the script and follow the instructions it prints at the end. If -you see an error saying that the 'python' command was not found, than -you may not have any version of Python installed. See: - - http://www.python.org/download/releases/2.7.3/#download - -for installation instructions. - -This test is separate to avoid Python syntax errors parsing the more -elaborate `swc-installation-test-2.py`. -""" - -import sys as _sys - - -__version__ = '0.1' - - -def check(): - if _sys.version_info < (2, 6): - print('check for Python version (python):') - print('outdated version of Python: ' + _sys.version) - return False - return True - - -if __name__ == '__main__': - if check(): - print('Passed') - else: - print('Failed') - print('Install a current version of Python!') - print('http://www.python.org/download/releases/2.7.3/#download') - _sys.exit(1) diff --git a/swc-main.py b/swc-main.py new file mode 100644 index 0000000..24fae0f --- /dev/null +++ b/swc-main.py @@ -0,0 +1,144 @@ +"""Test script to check for required functionality. + +Execute this code at the command line by typing: + + python swc-main.py + +Run the script and follow the instructions it prints at the end. + +This script requires at least Python 2.6. It will first check for the +existing version of python installed on your system, and will proceed further +only if you have at least Python 2.6. + +By default, this script will test for all the dependencies your +instructor thinks you need. If you want to test for a different set +of packages, you can list them on the command line. For example: + + python swc-main.py git virtual-editor + + This is useful if the original test told you to install a more recent + version of a particular dependency, and you just want to re-test that + dependency. + """ +# Some details about the implementation: + +# CHECKS list stores a list of the dependencies which are to be checked in +# the current workshop. + +# In the "__name__ == '__main__'" block, we launch all the checks with +# check() function of requirements_check.py, which prints information about the tests as they run +# and details about the failures after the tests complete. In case of +# failure, the functions print_system_info() and print_suggestions() +# are called after this, where the former prints information about the +# user's system for debugging purposes while the latter prints some +# suggestions to follow. + + +import requirements_check +from requirements_check import InvalidCheck, CHECKER +import api as API +import optparse as _optparse +import sys as _sys + +HOST = "installation.software-carpentry.org" + +# Comment out any entries you don't need +CHECKS = [ + # Shell + 'virtual-shell', + # Editors + 'virtual-editor', + # Browsers + 'virtual-browser', + # Version control + 'git', + 'hg', # Command line tool + # 'mercurial', # Python package + 'EasyMercurial', + # Build tools and packaging + 'make', + 'virtual-pypi-installer', + 'setuptools', + # 'xcode', + # Testing + 'nosetests', # Command line tool + 'nose', # Python package + 'py.test', # Command line tool + 'pytest', # Python package + # SQL + 'sqlite3', # Command line tool + 'sqlite3-python', # Python package + # Python + 'python', + 'ipython', # Command line tool + 'IPython', # Python package + 'argparse', # Useful for utility scripts + 'numpy', + 'scipy', + 'matplotlib', + 'pandas', + # 'sympy', + # 'Cython', + # 'networkx', + # 'mayavi.mlab', +] + + +def python_version_check(): + print("Checking for python version...") + if _sys.version_info < (2, 6): + print('check for Python version (python):') + print('outdated version of Python: ' + _sys.version) + return False + return True + +if __name__ == '__main__': + + if python_version_check(): + print('Passed') + else: + print('Failed') + print('Install the newest version from https://www.python.org/downloads/ or ask instructors for help') + _sys.exit(0) + + parser = _optparse.OptionParser(usage='%prog [options] [check...]') + epilog = __doc__ + parser.format_epilog = lambda formatter: '\n' + epilog + parser.add_option( + '-v', '--verbose', action='store_true', + help=('print additional information to help troubleshoot ' + 'installation issues')) + parser.add_option( + '-H', '--host', action='store', type="string", + help=('Change the server to which the data will be sent'), dest="host_name") + parser.add_option( + '-n', '--no_reporting', action='store_true', + help=('Turn off sending the data to server')) + options, args = parser.parse_args() + try: + if not args: + args = CHECKS + passed, successes_list, failures_list = requirements_check.check(args) + """Check whether host name is specified as a command line argument""" + if options.host_name: + HOST = options.host_name + """Check whether sending data to server is turned off using + command line argument""" + if options.no_reporting is None: + API.submit(successes_list, failures_list, HOST) + except InvalidCheck as e: + print("I don't know how to check for {0!r}".format(e.check)) + print('I do know how to check for:') + for key, checker in sorted(CHECKER.items()): + if checker.long_name != checker.name: + print(' {0} {1}({2})'.format( + key, ' ' * (20 - len(key)), checker.long_name)) + else: + print(' {0}'.format(key)) + _sys.exit(0) + if not passed: + if options.verbose: + print() + requirements_check.print_system_info() + requirements_check.print_suggestions(instructor_fallback=True) + _sys.exit(0)