From f7a38d4e44581ec8514da8e80976c79929b46528 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 15 Dec 2015 16:41:48 -0600 Subject: [PATCH] Add tests for Atomic diff and top Basic tests for atomic diff and top which should catch basic code regressions. In top.py, added -n for number of iterations. And added tty detection so that tests can pass in a jenkins environment where there is no tty. --- Atomic/atomic.py | 13 --------- Atomic/top.py | 27 ++++++++++++------- atomic | 9 +++++++ bash/atomic | 1 + docs/atomic-top.1.md | 9 ++++--- tests/integration/test_diff.sh | 48 ++++++++++++++++++++++++++++++++++ tests/integration/test_top.sh | 46 ++++++++++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 25 deletions(-) create mode 100755 tests/integration/test_diff.sh create mode 100755 tests/integration/test_top.sh diff --git a/Atomic/atomic.py b/Atomic/atomic.py index 3ac8ecf..d131ed2 100644 --- a/Atomic/atomic.py +++ b/Atomic/atomic.py @@ -1031,19 +1031,6 @@ class AtomicDocker(docker.Client): A class based on the docker client class with custom APIs specifically for atomic """ - def atomic_top(self, container, ps_args=None): - """ - Same as the docker top API but allows passing of ps args - :param container: container ID - :param ps_args: custom ps args - :return: - """ - u = self._url("/containers/{0}/top".format(container)) - params = {} - if ps_args is not None: - params['ps_args'] = ps_args - return self._result(self._get(u, params=params), True) - def remote_inspect(self, image_id): return self._result(self._get(self._url("/images/{0}/json?remote=1". format(image_id))), True) diff --git a/Atomic/top.py b/Atomic/top.py index 568f563..3a9de68 100644 --- a/Atomic/top.py +++ b/Atomic/top.py @@ -5,6 +5,7 @@ import tty import sys import termios import select +from os import isatty from operator import itemgetter @@ -86,11 +87,15 @@ class Top(Atomic): """ # Activate optional columns self._activate_optionals() - + # Do we have a tty? + # Can run ./atomic top <&- to replicate no tty + has_tty = isatty(0) # Set up terminal, input handling - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) + if has_tty: + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) sort_vals = [x['character'].upper() for x in self.headers if x['active'] and x['sort']] + counter = 0 while True: proc_info = [] if len(self.args.containers) < 1: @@ -100,14 +105,15 @@ class Top(Atomic): for cid in con_ids: proc_info += self.get_pids_by_container(cid) # Reset screen - if not self.DEBUG: + if not self.DEBUG and has_tty: util.writeOut("\033c") sorted_info = self.reformat_ps_info(proc_info) self._set_dynamic_column_widths(sorted_info) self.output_top(sorted_info) - tty.setraw(sys.stdin.fileno()) + if has_tty: + tty.setraw(sys.stdin.fileno()) i, ii, iii = select.select([sys.stdin], [], [], self.args.d) - if i: + if i and has_tty: ch = sys.stdin.read(1) # Detect 'q' or Cntrl-c if ch.upper() == 'q'.upper() or ord(ch) == 3: @@ -120,7 +126,11 @@ class Top(Atomic): None and header['character'].upper() == ch.upper()), False) # reset terminal - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + if has_tty: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + counter += 1 + if counter == self.args.n: + raise SystemExit def get_pids_by_container(self, con_id): """ @@ -138,8 +148,7 @@ class Top(Atomic): # Assemble the ps args ps_args = [header['ps_opt']for header in sorted(self.headers, key=itemgetter('index')) if header['ps_opt'] is not None and header['active']] - con_procs = self.AD.atomic_top(con_id, ps_args="o {}".format(",".join(ps_args))) - + con_procs = self.AD.top(con_id, ps_args="-o{}".format(",".join(ps_args))) # Set the column header titles one-time if self.titles is None: self.titles = con_procs['Titles'] diff --git a/atomic b/atomic index 2b52826..406c246 100755 --- a/atomic +++ b/atomic @@ -93,6 +93,14 @@ def add_opt(sub): sub.add_argument("--opt2", dest="opt2",help=argparse.SUPPRESS) sub.add_argument("--opt3", dest="opt3",help=argparse.SUPPRESS) + +def check_negative(value): + ivalue = int(value) + if ivalue < 1: + raise argparse.ArgumentTypeError("%s must be a positive integer value greater than 0." % value) + return ivalue + + if __name__ == '__main__': if os.geteuid() != 0: exit("%s needs to be run as root." % sys.argv[0] ) @@ -423,6 +431,7 @@ if __name__ == '__main__': topp.set_defaults(_class=Top, func='atomic_top') topp.add_argument("-d", type=int, default=1, help=_("Interval (secs) to refresh process information")) topp.add_argument("-o", "--optional", help=_("Additional fields to display"), nargs='*', choices=['time', 'stime', 'ppid']) + topp.add_argument("-n", help=_("Number of iterations"), type=check_negative) topp.add_argument("containers", nargs="*", help=_("list of containers to monitor, leave blank for all")) # atomic uninstall diff --git a/bash/atomic b/bash/atomic index a54727b..6b612c1 100644 --- a/bash/atomic +++ b/bash/atomic @@ -207,6 +207,7 @@ _atomic_top() { --optional -o -d + -n " local all_options="$options_with_args" [ "$command" = "top" ] && all_options="$all_options" diff --git a/docs/atomic-top.1.md b/docs/atomic-top.1.md index 2e9930d..cafffc8 100644 --- a/docs/atomic-top.1.md +++ b/docs/atomic-top.1.md @@ -6,7 +6,7 @@ atomic-top - Run a top-like list of active container processes # SYNOPSIS **atomic top** [**-h**|**--help**] -[**-d**][**-o, --optional=[time, stime, ppid]**] +[**-d**][**-o, --optional=[time, stime, ppid]**][**-n**] [Containers to monitor] # DESCRIPTION @@ -28,6 +28,9 @@ Like top, you can exit the interactive view and return to the command line, use Define the interval in seconds on which you want to refresh the process information. The interval should be an integer greater than 0. The default interval is set to 1. +**-n** + The number of iterations. Must be greater than 0. + **-o** **--optional** Add more fields of data to collect for each process. The fields resemble fields commonly used by ps -o. They currently are: [time, stime, ppid] @@ -37,9 +40,9 @@ Monitor processes with default fields. atomic top -Monitor processes with default fields on a 5 second interval. +Monitor processes with default fields on a 5 second interval for 3 interations - atomic top -d 5 + atomic top -d 5 -n 3 Monitor processes and add in the data for the parent PIDs. diff --git a/tests/integration/test_diff.sh b/tests/integration/test_diff.sh new file mode 100755 index 0000000..4b5800e --- /dev/null +++ b/tests/integration/test_diff.sh @@ -0,0 +1,48 @@ +#!/bin/bash -x +set -euo pipefail +IFS=$'\n\t' + +# Test scripts run with PWD=tests/.. + +# The test harness exports some variables into the environment during +# testing: PYTHONPATH (python module import path +# WORK_DIR (a directory that is safe to modify) +# DOCKER (the docker executable location) +# ATOMIC (an invocation of 'atomic' which measures code coverage) +# SECRET (a generated sha256 hash inserted into test containers) + +# In addition, the test harness creates some images for use in testing. +# See tests/test-images/ + +setup () { + # Perform setup routines here. + IMAGE="atomic-test-1" + id1=`${DOCKER} create ${IMAGE} /bin/true` + id2=`${DOCKER} create ${IMAGE} rpm -e vim-minimal` + ${DOCKER} start ${id2} + +} + +teardown () { + # Cleanup your test data. + set +e + ${DOCKER} rm ${id1} + ${DOCKER} rm ${id2} + set -e +} +# Utilize exit traps for cleanup wherever possible. Additional cleanup +# logic can be added to a "cleanup stack", by cascading function calls +# within traps. See tests/integration/test_mount.sh for an example. +trap teardown EXIT + +setup + +OUTPUT=$(/bin/true) + +# Test atomic diff for files and RPMs +${ATOMIC} diff -r -v ${id1} ${id2} 1>/dev/null + +# Test atomic diff with RPMs and output to json +${ATOMIC} diff -r --json ${id1} ${id2} 1>/dev/null + + diff --git a/tests/integration/test_top.sh b/tests/integration/test_top.sh new file mode 100755 index 0000000..1aa9820 --- /dev/null +++ b/tests/integration/test_top.sh @@ -0,0 +1,46 @@ +#!/bin/bash -x +set -euo pipefail +IFS=$'\n\t' + +# Test scripts run with PWD=tests/.. + +# The test harness exports some variables into the environment during +# testing: PYTHONPATH (python module import path +# WORK_DIR (a directory that is safe to modify) +# DOCKER (the docker executable location) +# ATOMIC (an invocation of 'atomic' which measures code coverage) +# SECRET (a generated sha256 hash inserted into test containers) + +# In addition, the test harness creates some images for use in testing. +# See tests/test-images/ + +setup () { + # Perform setup routines here. + IMAGE="atomic-test-1" + id1=`${DOCKER} run -d ${IMAGE} /usr/bin/vi` + id2=`${DOCKER} run -d ${IMAGE} /usr/bin/top` + +} + +teardown () { + # Cleanup your test data. + set +e + ${DOCKER} stop ${id1} + ${DOCKER} rm ${id1} + ${DOCKER} stop ${id2} + ${DOCKER} rm ${id2} + set -e +} +# Utilize exit traps for cleanup wherever possible. Additional cleanup +# logic can be added to a "cleanup stack", by cascading function calls +# within traps. See tests/integration/test_mount.sh for an example. +trap teardown EXIT + +setup + +OUTPUT=$(/bin/true) + +${ATOMIC} top -n 1 + + +