Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WHYIS_IMAGE="${1:-tetherlessworld/whyis:latest}"

mkdir -p test-results/py

# docker run $WHYIS_IMAGE bash -c "python3 manage.py test"
#docker run $WHYIS_IMAGE bash -c "python3 manage.py test"
# docker run $WHYIS_IMAGE bash -c "python3 manage.py test --ci && cat test-results/py/results.xml"

docker run $WHYIS_IMAGE bash -c "python3 manage.py test --ci &>/dev/null && tar cf test-results-py.tar test-results/py && cat test-results-py.tar" >test-results-py.tar
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ target/
.ipynb_checkpoints

venv/
tmpvenv/
blazegraph.jar
blazegraph.jnl

Expand Down
3 changes: 2 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import flask_script as script

import commands
from whyis import commands

from main import app_factory
try:
Expand All @@ -27,5 +27,6 @@
manager.add_command("test", commands.Test())
manager.add_command("test_agent", commands.TestAgent())
manager.add_command("updateuser", commands.UpdateUser())
manager.add_command("uninstall_app", commands.UninstallApp())

manager.run()
Empty file.
Empty file.
Empty file.
27 changes: 27 additions & 0 deletions tests/unit/whyis_test/commands/test_uninstall_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os.path
import subprocess
import sys
import tempfile
import venv

from whyis.test.temporary_app import TemporaryApp
from whyis.test.temporary_venv import TemporaryVenv
from whyis.test.unit_test_case import UnitTestCase




class TestUninstallApp(UnitTestCase):
def test_run(self):
if sys.platform.startswith("win"):
return

with TemporaryVenv() as temp_venv:
with TemporaryApp() as temp_app:
temp_app.install(venv_dir_path=temp_venv.venv_dir_path)
self.assertTrue(temp_venv.is_whyis_app_installed())

# Uninstall
self.assertEqual(0, subprocess.call("bash -c 'source %s/bin/activate && python manage.py uninstall_app -y'" % (temp_venv.venv_dir_path,), cwd=temp_venv.whyis_dir_path, shell=True))

self.assertFalse(temp_venv.is_whyis_app_installed())
1 change: 1 addition & 0 deletions whyis/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .test import Test
from .test_agent import TestAgent
from .update_user import UpdateUser
from .uninstall_app import UninstallApp
67 changes: 67 additions & 0 deletions whyis/commands/uninstall_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import subprocess
import ast
import logging
import os.path

from flask_script import Command, Option


class UninstallApp(Command):
"""
Uninstall the current whyis application.
"""

def get_options(self):
return [
Option('--yes', '-y', dest='yes', action='store_true',
default=False)
]


def run(self, yes: bool):
try:
import config
except ImportError:
logging.warning("unable to import config; no application installed?")
return

app_dir_path = os.path.dirname(config.__file__)
setup_py_file_path = os.path.join(app_dir_path, "setup.py")
if not os.path.isfile(setup_py_file_path):
logging.warning("%s does not exist")
return

project_name = None
with open(setup_py_file_path) as setup_py_file:
ast_ = ast.parse(setup_py_file.read(), setup_py_file.name)

class SetupNodeVisitor(ast.NodeVisitor):
def __init__(self):
ast.NodeVisitor.__init__(self)
self.project_name = None

def visit(self, node):
if isinstance(node, ast.Call):
call_node = node
if hasattr(call_node, "func") and call_node.func.id == "setup" and hasattr(call_node, "keywords"):
for keyword_node in call_node.keywords:
if keyword_node.arg == "name" and isinstance(keyword_node.value, ast.Str):
self.project_name = keyword_node.value.s
break

self.generic_visit(node)
visitor = SetupNodeVisitor()
visitor.visit(ast_)
project_name = visitor.project_name
if project_name is None:
logging.warning("unable to parse project name from setup.py")
return
project_id = project_name.replace(" ", "-")
logging.info("App project: %s (%s)", project_name, project_id)

pip_args = ["pip", "uninstall"]
if yes:
pip_args.append("-y")
pip_args.append(project_id)
logging.info(" ".join(pip_args))
subprocess.call(pip_args)
43 changes: 43 additions & 0 deletions whyis/test/temporary_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os.path
import shutil
import subprocess
import tempfile


class TemporaryApp:
def __init__(self, delete=True):
self.__delete = delete
self.app_dir_path = None

def __enter__(self):
self.app_dir_path = tempfile.mkdtemp()

with open(os.path.join(self.app_dir_path, "setup.py"), "w+") as setup_py_file:
setup_py_file.write("""\
from distutils.core import setup

setup(name='TempApp',
version='0.1',
description='Temporary app',
author='rui',
packages=[

],
)
""")

with open(os.path.join(self.app_dir_path, "config.py"), "w+") as config_py_file:
config_py_file.write("""\
from config_defaults import *
""")

# print(self.app_dir_path)

return self

def __exit__(self, *args, **kwds):
if self.__delete:
shutil.rmtree(self.app_dir_path)

def install(self, venv_dir_path: str):
subprocess.call("%s/bin/pip install -e ." % venv_dir_path, cwd=self.app_dir_path, shell=True)
49 changes: 49 additions & 0 deletions whyis/test/temporary_venv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os.path
import shutil
import subprocess
import tempfile
import venv

class TemporaryVenv:
def __init__(self, delete=True, install_whyis_requirements=True, whyis_dir_path=None):
self.__delete = delete
self.__install_whyis_requirements = install_whyis_requirements

self.venv_dir_path = None

if whyis_dir_path is None:
import manage
whyis_dir_path = os.path.dirname(manage.__file__)
self.whyis_dir_path = whyis_dir_path

def __enter__(self):
temp_venv_dir_path = os.path.join(self.whyis_dir_path, "tmpvenv")
if not os.path.isdir(temp_venv_dir_path):
os.makedirs(temp_venv_dir_path)
self.venv_dir_path = tempfile.mkdtemp(dir=temp_venv_dir_path)
# print(self.venv_dir_path)
venv.create(self.venv_dir_path)
# with_pip doesn't work at present, so have to install pip into it manually
subprocess.call("curl https://bootstrap.pypa.io/get-pip.py | %s/bin/python" % self.venv_dir_path, cwd=self.whyis_dir_path, shell=True)
if self.__install_whyis_requirements:
self.install_whyis_requirements()
return self

def __exit__(self, *args, **kwds):
if self.__delete:
shutil.rmtree(self.venv_dir_path)

def install_whyis_requirements(self) -> None:
# Install every requirement separately so that failed installations are skipped
# subprocess.call("cat requirements/common.txt | xargs -n 1 %s/bin/pip install" % self.venv_dir_path, cwd=self.whyis_dir_path, shell=True)
with tempfile.NamedTemporaryFile(delete=False, mode="w") as temp_requirements_txt:
with open(os.path.join(self.whyis_dir_path, "requirements", "common.txt")) as requirements_file:
for line in requirements_file:
if line.startswith("javabridge"):
continue
temp_requirements_txt.write(line)
temp_requirements_txt.flush()
subprocess.call("%s/bin/pip install -r %s" % (self.venv_dir_path, temp_requirements_txt.name), cwd=self.whyis_dir_path, shell=True)

def is_whyis_app_installed(self) -> bool:
return subprocess.call("%s/bin/python -c 'import config'" % self.venv_dir_path, cwd=self.whyis_dir_path, shell=True) == 0