Merge branch '2017.7' into service_test

This commit is contained in:
Nicole Thomas 2017-10-29 11:32:33 -04:00 committed by GitHub
commit 716aabc0bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1167 additions and 207 deletions

5
.gitignore vendored
View File

@ -91,3 +91,8 @@ tests/integration/cloud/providers/pki/minions
# Ignore tox virtualenvs
/.tox/
# Ignore kitchen stuff
.kitchen
.bundle
Gemfile.lock

175
.kitchen.yml Normal file
View File

@ -0,0 +1,175 @@
---
<% vagrant = system('which vagrant 2>/dev/null >/dev/null') %>
<% version = '2016.11.8' %>
<% platformsfile = ENV['SALT_KITCHEN_PLATFORMS'] || '.kitchen/platforms.yml' %>
<% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %>
<% if File.exists?(driverfile) %>
<%= File.read(driverfile) %>
<% else %>
driver:
name: docker
use_sudo: false
privileged: true
username: root
volume:
- /var/run/docker.sock:/docker.sock
cap_add:
- sys_admin
disable_upstart: false
provision_command:
- echo 'L /run/docker.sock - - - - /docker.sock' > /etc/tmpfiles.d/docker.conf
<% end %>
sudo: false
provisioner:
name: salt_solo
salt_install: bootstrap
salt_version: latest
salt_bootstrap_url: https://bootstrap.saltstack.com
salt_bootstrap_options: -X stable <%= version %>
log_level: info
require_chef: false
remote_states:
name: git://github.com/gtmanfred/salt-jenkins.git
branch: 2016.11
repo: git
testingdir: /testing
salt_copy_filter:
- .bundle
- .git
- .gitignore
- .kitchen
- .kitchen.yml
- Gemfile
- Gemfile.lock
- README.rst
- .travis.yml
state_top:
base:
"*":
- git.salt
- kitchen
<% if File.exists?(platformsfile) %>
<%= File.read(platformsfile) %>
<% else %>
platforms:
- name: fedora
driver_config:
image: fedora:latest
run_command: /usr/lib/systemd/systemd
provisioner:
salt_bootstrap_options: -X git v<%= version %> >/dev/null
- name: centos-7
driver_config:
run_command: /usr/lib/systemd/systemd
- name: centos-6
driver_config:
run_command: /sbin/init
provision_command:
- yum install -y upstart
provisioner:
salt_bootstrap_options: -P -y -x python2.7 -X git v<%= version %> >/dev/null
- name: ubuntu-rolling
driver_config:
image: ubuntu:rolling
run_command: /lib/systemd/systemd
provisioner:
salt_bootstrap_url: https://raw.githubusercontent.com/saltstack/salt-bootstrap/develop/bootstrap-salt.sh
- name: ubuntu-16.04
driver_config:
run_command: /lib/systemd/systemd
- name: ubuntu-14.04
driver_config:
run_command: /sbin/init
provision_command:
- rm -f /sbin/initctl
- dpkg-divert --local --rename --remove /sbin/initctl
- name: debian-8
driver_config:
run_command: /lib/systemd/systemd
provision_command:
- apt-get install -y dbus
- echo 'L /run/docker.sock - - - - /docker.sock' > /etc/tmpfiles.d/docker.conf
- name: debian-9
driver_config:
run_command: /lib/systemd/systemd
- name: arch
driver_config:
image: base/archlinux
run_command: /usr/lib/systemd/systemd
provision_command:
- pacman -Syu --noconfirm systemd
- systemctl enable sshd
- echo 'L /run/docker.sock - - - - /docker.sock' > /etc/tmpfiles.d/docker.conf
provisioner:
salt_bootstrap_options: -X git v<%= version %> >/dev/null
- name: opensuse
driver_config:
run_command: /usr/lib/systemd/systemd
provision_command:
- systemctl enable sshd.service
- echo 'L /run/docker.sock - - - - /docker.sock' > /etc/tmpfiles.d/docker.conf
provisioner:
salt_bootstrap_options: -X git v<%= version %> >/dev/null
<% if vagrant != false %>
- name: windows-2012r2
driver:
box: mwrock/Windows2012R2
communicator: winrm
name: vagrant
gui: true
username: administrator
password: Pass@word1
provisioner:
init_environment: |
Clear-Host
$AddedLocation ="c:\salt"
$Reg = "Registry::HKLM\System\CurrentControlSet\Control\Session Manager\Environment"
$OldPath = (Get-ItemProperty -Path "$Reg" -Name PATH).Path
$NewPath= $OldPath + ; + $AddedLocation
Set-ItemProperty -Path "$Reg" -Name PATH Value $NewPath
salt_bootstrap_url: https://raw.githubusercontent.com/saltstack/salt-bootstrap/develop/bootstrap-salt.ps1
salt_bootstrap_options: ''
- name: windows-2016
driver:
box: mwrock/Windows2016
communicator: winrm
name: vagrant
username: Vagrant
password: vagrant
gui: true
provisioner:
init_environment: |
Clear-Host
$AddedLocation ="c:\salt;c:\salt\bin\Scripts"
$Reg = "Registry::HKLM\System\CurrentControlSet\Control\Session Manager\Environment"
$OldPath = (Get-ItemProperty -Path "$Reg" -Name PATH).Path
$NewPath= $OldPath + ; + $AddedLocation
Set-ItemProperty -Path "$Reg" -Name PATH Value $NewPath
salt_bootstrap_url: https://raw.githubusercontent.com/saltstack/salt-bootstrap/develop/bootstrap-salt.ps1
salt_bootstrap_options: ''
<% end %>
<% end %>
suites:
- name: py2
provisioner:
pillars:
top.sls:
base:
"*":
- jenkins
jenkins.sls:
testing_dir: /tmp/kitchen/testing
clone_repo: false
salttesting_namespec: salttesting==2017.6.1
verifier:
name: shell
remote_exec: true
sudo: false
live_stream: {}
<% if ENV['TESTOPTS'].nil? %>
command: '$(kitchen) /tmp/kitchen/testing/tests/runtests.py --run-destructive --sysinfo --transport=zeromq --output-columns=80 --ssh --coverage-xml=/tmp/coverage.xml --xml=/tmp/xml-unittests-output'
<% else %>
command: '$(kitchen) /tmp/kitchen/testing/tests/runtests.py --run-destructive --output-columns 80 <%= ENV["TESTOPTS"] %>'
<% end %>

23
Gemfile Normal file
View File

@ -0,0 +1,23 @@
# This file is only used for running the test suite with kitchen-salt.
source "https://rubygems.org"
gem "test-kitchen"
gem "kitchen-salt", :git => 'https://github.com/gtmanfred/kitchen-salt.git'
gem 'git'
group :docker do
gem 'kitchen-docker', :git => 'https://github.com/test-kitchen/kitchen-docker.git'
end
group :opennebula do
gem 'kitchen-opennebula', :git => 'https://github.com/gtmanfred/kitchen-opennebula.git'
gem 'xmlrpc'
end
group :windows do
gem 'vagrant-wrapper'
gem 'kitchen-vagrant'
gem 'winrm', '~>2.0'
gem 'winrm-fs', '~>1.0'
end

View File

@ -95,19 +95,19 @@ globally available or passed in through function arguments, file data, etc.
Mocking Loader Modules
----------------------
Salt loader modules use a series of globally available dunder variables,
``__salt__``, ``__opts__``, ``__pillar__``, etc. To facilitate testing these
modules a mixin class was created, ``LoaderModuleMockMixin`` which can be found
in ``tests/support/mixins.py``. The reason for the exitance of this class is
because, historycally, and because it was easier, one would add these dunder
variables directly on the imported module. This however, introduces unexpected
behavior when running the full test suite since those attributes would not be
removed once we were done testing the module and would therefor leak to other
modules being tested with unpredictable results. This is the kind of work that
should be defered to mock, and that's exactly what this mixin class does.
Salt loader modules use a series of globally available dunder variables,
``__salt__``, ``__opts__``, ``__pillar__``, etc. To facilitate testing these
modules a mixin class was created, ``LoaderModuleMockMixin`` which can be found
in ``tests/support/mixins.py``. The reason for the existance of this class is
because historiclly and because it was easier, one would add these dunder
variables directly on the imported module. This however, introduces unexpected
behavior when running the full test suite since those attributes would not be
removed once we were done testing the module and would therefore leak to other
modules being tested with unpredictable results. This is the kind of work that
should be deferred to mock, and that's exactly what this mixin class does.
As an example, if one needs to specify some options which should be available
to the module being tests one should do:
As an example, if one needs to specify some options which should be available
to the module being tested one should do:
.. code-block:: python
@ -122,7 +122,7 @@ to the module being tests one should do:
}
}
Consider this more extensive example from
Consider this more extensive example from
``tests/unit/modules/test_libcloud_dns.py``:
.. code-block:: python
@ -173,10 +173,10 @@ Consider this more extensive example from
return {libcloud_dns: module_globals}
What happens on the above example is that, we mock a call to
`__salt__['config.option']` to return the configuration needed for the
execution of the tests. Additionally, if the ``libcloud`` library is not
available, since that's not actually part of whats being tested, we mocked that
What happens in the above example is we mock a call to
`__salt__['config.option']` to return the configuration needed for the
execution of the tests. Additionally, if the ``libcloud`` library is not
available, since that's not actually part of what's being tested, we mocked that
import by patching ``sys.modules`` when tests are running.
@ -245,7 +245,7 @@ To understand how one might integrate Mock into writing a unit test for Salt,
let's imagine a scenario in which we're testing an execution module that's
designed to operate on a database. Furthermore, let's imagine two separate
methods, here presented in pseduo-code in an imaginary execution module called
'db.py.
'db.py'.
.. code-block:: python

View File

@ -9,7 +9,7 @@ Module to provide redis functionality to Salt
.. code-block:: yaml
redis.host: 'localhost'
redis.host: 'salt'
redis.port: 6379
redis.db: 0
redis.password: None

View File

@ -36,6 +36,60 @@ def __virtual__():
return (False, "Module win_groupadd: module only works on Windows systems")
def _get_computer_object():
'''
A helper function to get the object for the local machine
Returns:
object: Returns the computer object for the local machine
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://.,computer')
def _get_group_object(name):
'''
A helper function to get a specified group object
Args:
name (str): The name of the object
Returns:
object: The specified group object
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://./' + name + ',group')
def _get_all_groups():
'''
A helper function that gets a list of group objects for all groups on the
machine
Returns:
iter: A list of objects for all groups on the machine
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
results = nt.GetObject('', 'WinNT://.')
results.Filter = ['group']
return results
def _get_username(member):
'''
Resolve the username from the member object returned from a group query
Returns:
str: The username converted to domain\\username format
'''
return member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace')
def add(name, **kwargs):
'''
Add the specified group
@ -60,10 +114,8 @@ def add(name, **kwargs):
'comment': ''}
if not info(name):
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
compObj = _get_computer_object()
try:
compObj = nt.GetObject('', 'WinNT://.,computer')
newGroup = compObj.Create('group', name)
newGroup.SetInfo()
ret['changes'].append('Successfully created group {0}'.format(name))
@ -104,10 +156,8 @@ def delete(name, **kwargs):
'comment': ''}
if info(name):
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
compObj = _get_computer_object()
try:
compObj = nt.GetObject('', 'WinNT://.,computer')
compObj.Delete('group', name)
ret['changes'].append(('Successfully removed group {0}').format(name))
except pywintypes.com_error as com_err:
@ -144,17 +194,10 @@ def info(name):
salt '*' group.info foo
'''
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
try:
groupObj = nt.GetObject('', 'WinNT://./' + name + ',group')
groupObj = _get_group_object(name)
gr_name = groupObj.Name
gr_mem = []
for member in groupObj.members():
gr_mem.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace'))
gr_mem = [_get_username(x) for x in groupObj.members()]
except pywintypes.com_error:
return False
@ -193,20 +236,12 @@ def getent(refresh=False):
ret = []
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
results = _get_all_groups()
results = nt.GetObject('', 'WinNT://.')
results.Filter = ['group']
for result in results:
member_list = []
for member in result.members():
member_list.append(
member.AdsPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace'))
group = {'gid': __salt__['file.group_to_gid'](result.name),
'members': member_list,
'name': result.name,
group = {'gid': __salt__['file.group_to_gid'](result.Name),
'members': [_get_username(x) for x in result.members()],
'name': result.Name,
'passwd': 'x'}
ret.append(group)
__context__['group.getent'] = ret
@ -240,17 +275,21 @@ def adduser(name, username, **kwargs):
'changes': {'Users Added': []},
'comment': ''}
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
groupObj = nt.GetObject('', 'WinNT://./' + name + ',group')
existingMembers = []
for member in groupObj.members():
existingMembers.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower())
try:
groupObj = _get_group_object(name)
except pywintypes.com_error as com_err:
if len(com_err.excepinfo) >= 2:
friendly_error = com_err.excepinfo[2].rstrip('\r\n')
ret['result'] = False
ret['comment'] = 'Failure accessing group {0}. {1}' \
''.format(name, friendly_error)
return ret
existingMembers = [_get_username(x) for x in groupObj.members()]
username = salt.utils.win_functions.get_sam_name(username)
try:
if salt.utils.win_functions.get_sam_name(username).lower() not in existingMembers:
if username not in existingMembers:
if not __opts__['test']:
groupObj.Add('WinNT://' + username.replace('\\', '/'))
@ -299,17 +338,20 @@ def deluser(name, username, **kwargs):
'changes': {'Users Removed': []},
'comment': ''}
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
groupObj = nt.GetObject('', 'WinNT://./' + name + ',group')
existingMembers = []
for member in groupObj.members():
existingMembers.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower())
try:
groupObj = _get_group_object(name)
except pywintypes.com_error as com_err:
if len(com_err.excepinfo) >= 2:
friendly_error = com_err.excepinfo[2].rstrip('\r\n')
ret['result'] = False
ret['comment'] = 'Failure accessing group {0}. {1}' \
''.format(name, friendly_error)
return ret
existingMembers = [_get_username(x) for x in groupObj.members()]
try:
if salt.utils.win_functions.get_sam_name(username).lower() in existingMembers:
if salt.utils.win_functions.get_sam_name(username) in existingMembers:
if not __opts__['test']:
groupObj.Remove('WinNT://' + username.replace('\\', '/'))
@ -365,10 +407,8 @@ def members(name, members_list, **kwargs):
ret['comment'].append('Members is not a list object')
return ret
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
try:
groupObj = nt.GetObject('', 'WinNT://./' + name + ',group')
groupObj = _get_group_object(name)
except pywintypes.com_error as com_err:
if len(com_err.excepinfo) >= 2:
friendly_error = com_err.excepinfo[2].rstrip('\r\n')
@ -377,12 +417,7 @@ def members(name, members_list, **kwargs):
'Failure accessing group {0}. {1}'
).format(name, friendly_error))
return ret
existingMembers = []
for member in groupObj.members():
existingMembers.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower())
existingMembers = [_get_username(x) for x in groupObj.members()]
existingMembers.sort()
members_list.sort()
@ -448,18 +483,14 @@ def list_groups(refresh=False):
salt '*' group.list_groups
'''
if 'group.list_groups' in __context__ and not refresh:
return __context__['group.getent']
return __context__['group.list_groups']
results = _get_all_groups()
ret = []
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
results = nt.GetObject('', 'WinNT://.')
results.Filter = ['group']
for result in results:
ret.append(result.name)
ret.append(result.Name)
__context__['group.list_groups'] = ret

View File

@ -40,6 +40,7 @@ Current known limitations
# Import python libs
from __future__ import absolute_import
from __future__ import unicode_literals
import io
import os
import logging
@ -4082,7 +4083,7 @@ def _write_regpol_data(data_to_write,
gpt_ini_data = ''
if os.path.exists(gpt_ini_path):
with salt.utils.fopen(gpt_ini_path, 'rb') as gpt_file:
gpt_ini_data = gpt_file.read()
gpt_ini_data = salt.utils.to_str(gpt_file.read())
if not _regexSearchRegPolData(r'\[General\]\r\n', gpt_ini_data):
gpt_ini_data = '[General]\r\n' + gpt_ini_data
if _regexSearchRegPolData(r'{0}='.format(re.escape(gpt_extension)), gpt_ini_data):
@ -4137,7 +4138,7 @@ def _write_regpol_data(data_to_write,
gpt_ini_data[general_location.end():])
if gpt_ini_data:
with salt.utils.fopen(gpt_ini_path, 'wb') as gpt_file:
gpt_file.write(gpt_ini_data)
gpt_file.write(salt.utils.to_bytes(gpt_ini_data))
except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(
gpt_ini_path, e)
@ -5375,7 +5376,7 @@ def set_(computer_policy=None, user_policy=None,
_regedits[regedit]['policy']['Registry']['Type'])
else:
_ret = __salt__['reg.delete_value'](
_regedits[regedit]['polic']['Registry']['Hive'],
_regedits[regedit]['policy']['Registry']['Hive'],
_regedits[regedit]['policy']['Registry']['Path'],
_regedits[regedit]['policy']['Registry']['Value'])
if not _ret:

View File

@ -2855,6 +2855,7 @@ def directory(name,
if __opts__['test']:
ret['result'] = presult
ret['comment'] = pcomment
ret['changes'] = ret['pchanges']
return ret
if not os.path.isdir(name):

View File

@ -1330,10 +1330,14 @@ def fopen(*args, **kwargs):
if len(args) > 1:
args = list(args)
if 'b' not in args[1]:
args[1] += 'b'
elif kwargs.get('mode', None):
args[1] = args[1].replace('t', 'b')
if 'b' not in args[1]:
args[1] += 'b'
elif kwargs.get('mode'):
if 'b' not in kwargs['mode']:
kwargs['mode'] += 'b'
kwargs['mode'] = kwargs['mode'].replace('t', 'b')
if 'b' not in kwargs['mode']:
kwargs['mode'] += 'b'
else:
# the default is to read
kwargs['mode'] = 'rb'

View File

@ -439,7 +439,7 @@ class GitProvider(object):
return root_dir
log.error(
'Root path \'%s\' not present in %s remote \'%s\', '
'skipping.', self.root, self.role, self.id
'skipping.', self.root(), self.role, self.id
)
return None

View File

@ -800,7 +800,10 @@ class TestDaemon(object):
# Set up config options that require internal data
master_opts['pillar_roots'] = syndic_master_opts['pillar_roots'] = {
'base': [os.path.join(FILES, 'pillar', 'base')]
'base': [
RUNTIME_VARS.TMP_PILLAR_TREE,
os.path.join(FILES, 'pillar', 'base'),
]
}
master_opts['file_roots'] = syndic_master_opts['file_roots'] = {
'base': [
@ -976,6 +979,7 @@ class TestDaemon(object):
sub_minion_opts['sock_dir'],
minion_opts['sock_dir'],
RUNTIME_VARS.TMP_STATE_TREE,
RUNTIME_VARS.TMP_PILLAR_TREE,
RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
TMP,
],
@ -1087,7 +1091,8 @@ class TestDaemon(object):
os.chmod(path, stat.S_IRWXU)
func(path)
for dirname in (TMP, RUNTIME_VARS.TMP_STATE_TREE, RUNTIME_VARS.TMP_PRODENV_STATE_TREE):
for dirname in (TMP, RUNTIME_VARS.TMP_STATE_TREE,
RUNTIME_VARS.TMP_PILLAR_TREE, RUNTIME_VARS.TMP_PRODENV_STATE_TREE):
if os.path.isdir(dirname):
shutil.rmtree(dirname, onerror=remove_readonly)

View File

@ -5,10 +5,16 @@ Integration tests for the saltutil module.
# Import Python libs
from __future__ import absolute_import
import os
import time
import textwrap
# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.paths import TMP_PILLAR_TREE
# Import Salt Libs
import salt.utils
class SaltUtilModuleTest(ModuleCase):
@ -153,3 +159,38 @@ class SaltUtilSyncModuleTest(ModuleCase):
ret = self.run_function('saltutil.sync_all', extmod_whitelist={'modules': ['runtests_decorators']},
extmod_blacklist={'modules': ['runtests_decorators']})
self.assertEqual(ret, expected_return)
class SaltUtilSyncPillarTest(ModuleCase):
'''
Testcase for the saltutil sync pillar module
'''
def test_pillar_refresh(self):
'''
test pillar refresh module
'''
pillar_key = 'itworked'
pre_pillar = self.run_function('pillar.raw')
self.assertNotIn(pillar_key, pre_pillar.get(pillar_key, 'didnotwork'))
with salt.utils.fopen(os.path.join(TMP_PILLAR_TREE, 'add_pillar.sls'), 'w') as fp:
fp.write('{0}: itworked'.format(pillar_key))
with salt.utils.fopen(os.path.join(TMP_PILLAR_TREE, 'top.sls'), 'w') as fp:
fp.write(textwrap.dedent('''\
base:
'*':
- add_pillar
'''))
pillar_refresh = self.run_function('saltutil.refresh_pillar')
wait = self.run_function('test.sleep', [1])
post_pillar = self.run_function('pillar.raw')
self.assertIn(pillar_key, post_pillar.get(pillar_key, 'didnotwork'))
def tearDown(self):
for filename in os.listdir(TMP_PILLAR_TREE):
os.remove(os.path.join(TMP_PILLAR_TREE, filename))

View File

@ -67,3 +67,23 @@ class ProxyMinionSimpleTestCase(ModuleCase):
ret = self.run_function('service.start', ['samba'], minion_tgt='proxytest')
ret = self.run_function('service.status', ['samba'], minion_tgt='proxytest')
self.assertTrue(ret)
def test_service_get_all(self):
ret = self.run_function('service.get_all', minion_tgt='proxytest')
self.assertTrue(ret)
self.assertIn('samba', ' '.join(ret))
def test_grains_items(self):
ret = self.run_function('grains.items', minion_tgt='proxytest')
self.assertEqual(ret['kernel'], 'proxy')
self.assertEqual(ret['kernelrelease'], 'proxy')
def test_state_apply(self):
ret = self.run_function('state.apply', ['core'], minion_tgt='proxytest')
for key, value in ret.items():
self.assertTrue(value['result'])
def test_state_highstate(self):
ret = self.run_function('state.highstate', minion_tgt='proxytest')
for key, value in ret.items():
self.assertTrue(value['result'])

View File

@ -6,49 +6,24 @@ Tests for the spm build utility
from __future__ import absolute_import
import os
import shutil
import textwrap
# Import Salt libs
import salt.utils
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.case import SPMCase, ModuleCase
from tests.support.helpers import destructiveTest
# Import Salt Libraries
import salt.utils
from tests.support.unit import skipIf
@destructiveTest
class SPMBuildTest(SPMCase):
class SPMBuildTest(SPMCase, ModuleCase):
'''
Validate the spm build command
'''
def setUp(self):
self.config = self._spm_config()
self.formula_dir = os.path.join(' '.join(self.config['file_roots']['base']), 'formulas')
self.formula_sls_dir = os.path.join(self.formula_dir, 'apache')
self.formula_sls = os.path.join(self.formula_sls_dir, 'apache.sls')
self.formula_file = os.path.join(self.formula_dir, 'FORMULA')
dirs = [self.formula_dir, self.formula_sls_dir]
for formula_dir in dirs:
os.makedirs(formula_dir)
with salt.utils.fopen(self.formula_sls, 'w') as fp:
fp.write(textwrap.dedent('''\
install-apache:
pkg.installed:
- name: apache2
'''))
with salt.utils.fopen(self.formula_file, 'w') as fp:
fp.write(textwrap.dedent('''\
name: apache
os: RedHat, Debian, Ubuntu, Suse, FreeBSD
os_family: RedHat, Debian, Suse, FreeBSD
version: 201506
release: 2
summary: Formula for installing Apache
description: Formula for installing Apache
'''))
self._spm_build_files(self.config)
def test_spm_build(self):
'''
@ -61,5 +36,44 @@ class SPMBuildTest(SPMCase):
# Make sure formula path dir is created
self.assertTrue(os.path.isdir(self.config['formula_path']))
@skipIf(salt.utils.which('fallocate') is None, 'fallocate not installed')
def test_spm_build_big_file(self):
'''
test spm build
'''
big_file = self.run_function('cmd.run',
['fallocate -l 1G {0}'.format(os.path.join(self.formula_sls_dir,
'bigfile.txt'))])
build_spm = self.run_spm('build', self.config, self.formula_dir)
spm_file = os.path.join(self.config['spm_build_dir'], 'apache-201506-2.spm')
install = self.run_spm('install', self.config, spm_file)
get_files = self.run_spm('files', self.config, 'apache')
files = ['apache.sls', 'bigfile.txt']
for sls in files:
self.assertIn(sls, ' '.join(get_files))
def test_spm_build_exclude(self):
'''
test spm build
'''
git_dir = os.path.join(self.formula_sls_dir, '.git')
os.makedirs(git_dir)
files = ['donotbuild1', 'donotbuild2', 'donotbuild3']
for git_file in files:
with salt.utils.fopen(os.path.join(git_dir, git_file), 'w') as fp:
fp.write('Please do not include me in build')
build_spm = self.run_spm('build', self.config, self.formula_dir)
spm_file = os.path.join(self.config['spm_build_dir'], 'apache-201506-2.spm')
install = self.run_spm('install', self.config, spm_file)
get_files = self.run_spm('files', self.config, 'apache')
for git_file in files:
self.assertNotIn(git_file, ' '.join(get_files))
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
'''
Tests for the spm files utility
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.helpers import destructiveTest
@destructiveTest
class SPMFilesTest(SPMCase):
'''
Validate the spm files command
'''
def setUp(self):
self.config = self._spm_config()
self._spm_build_files(self.config)
def test_spm_files(self):
'''
test spm files
'''
self._spm_create_update_repo(self.config)
install = self.run_spm('install', self.config, 'apache')
get_files = self.run_spm('files', self.config, 'apache')
os.path.exists(os.path.join(self.config['formula_path'], 'apache',
'apache.sls'))
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
'''
Tests for the spm info utility
'''
# Import python libs
from __future__ import absolute_import
import shutil
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.helpers import destructiveTest
@destructiveTest
class SPMInfoTest(SPMCase):
'''
Validate the spm info command
'''
def setUp(self):
self.config = self._spm_config()
self._spm_build_files(self.config)
def test_spm_info(self):
'''
test spm build
'''
self._spm_create_update_repo(self.config)
install = self.run_spm('install', self.config, 'apache')
get_info = self.run_spm('info', self.config, 'apache')
check_info = ['Supported OSes', 'Supported OS', 'installing Apache']
for info in check_info:
self.assertIn(info, ''.join(get_info))
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
'''
Tests for the spm install utility
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.helpers import destructiveTest
@destructiveTest
class SPMInstallTest(SPMCase):
'''
Validate the spm install command
'''
def setUp(self):
self.config = self._spm_config()
self._spm_build_files(self.config)
def test_spm_install_local_dir(self):
'''
test spm install from local directory
'''
build_spm = self.run_spm('build', self.config, self.formula_dir)
spm_file = os.path.join(self.config['spm_build_dir'],
'apache-201506-2.spm')
install = self.run_spm('install', self.config, spm_file)
sls = os.path.join(self.config['formula_path'], 'apache', 'apache.sls')
self.assertTrue(os.path.exists(sls))
def test_spm_install_from_repo(self):
'''
test spm install from repo
'''
self._spm_create_update_repo(self.config)
install = self.run_spm('install', self.config, 'apache')
sls = os.path.join(self.config['formula_path'], 'apache', 'apache.sls')
self.assertTrue(os.path.exists(sls))
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
'''
Tests for the spm remove utility
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.helpers import destructiveTest
@destructiveTest
class SPMRemoveTest(SPMCase):
'''
Validate the spm remove command
'''
def setUp(self):
self.config = self._spm_config()
self._spm_build_files(self.config)
def test_spm_remove(self):
'''
test spm remove from an inital repo install
'''
# first install apache package
self._spm_create_update_repo(self.config)
install = self.run_spm('install', self.config, 'apache')
sls = os.path.join(self.config['formula_path'], 'apache', 'apache.sls')
self.assertTrue(os.path.exists(sls))
#now remove an make sure file is removed
remove = self.run_spm('remove', self.config, 'apache')
sls = os.path.join(self.config['formula_path'], 'apache', 'apache.sls')
self.assertFalse(os.path.exists(sls))
self.assertIn('... removing apache', remove)
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
'''
Tests for the spm repo
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
# Import Salt Testing libs
from tests.support.case import SPMCase
from tests.support.helpers import destructiveTest
@destructiveTest
class SPMRepoTest(SPMCase):
'''
Validate commands related to spm repo
'''
def setUp(self):
self.config = self._spm_config()
self._spm_build_files(self.config)
def test_spm_create_update_repo(self):
'''
test spm create_repo
'''
self._spm_create_update_repo(self.config)
self.assertTrue(os.path.exists(self.config['spm_db']))
l_repo_file = os.path.join(self.config['spm_cache_dir'], 'local_repo.p')
self.assertTrue(os.path.exists(l_repo_file))
def tearDown(self):
shutil.rmtree(self._tmp_spm)

View File

@ -130,6 +130,9 @@ TEST_SUITES = {
'returners':
{'display_name': 'Returners',
'path': 'integration/returners'},
'spm':
{'display_name': 'SPM',
'path': 'integration/spm'},
'loader':
{'display_name': 'Loader',
'path': 'integration/loader'},
@ -338,6 +341,13 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
action='store_true',
help='Run salt/returners/*.py tests'
)
self.test_selection_group.add_option(
'--spm',
dest='spm',
default=False,
action='store_true',
help='Run spm integration tests'
)
self.test_selection_group.add_option(
'-l',
'--loader',

View File

@ -22,6 +22,7 @@ import time
import stat
import errno
import signal
import textwrap
import logging
import tempfile
import subprocess
@ -564,11 +565,60 @@ class ShellCase(ShellTestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixi
timeout=timeout)
class SPMTestUserInterface(object):
'''
Test user interface to SPMClient
'''
def __init__(self):
self._status = []
self._confirm = []
self._error = []
def status(self, msg):
self._status.append(msg)
def confirm(self, action):
self._confirm.append(action)
def error(self, msg):
self._error.append(msg)
class SPMCase(TestCase, AdaptedConfigurationTestCaseMixin):
'''
Class for handling spm commands
'''
def _spm_build_files(self, config):
self.formula_dir = os.path.join(' '.join(config['file_roots']['base']), 'formulas')
self.formula_sls_dir = os.path.join(self.formula_dir, 'apache')
self.formula_sls = os.path.join(self.formula_sls_dir, 'apache.sls')
self.formula_file = os.path.join(self.formula_dir, 'FORMULA')
dirs = [self.formula_dir, self.formula_sls_dir]
for f_dir in dirs:
os.makedirs(f_dir)
import salt.utils
with salt.utils.fopen(self.formula_sls, 'w') as fp:
fp.write(textwrap.dedent('''\
install-apache:
pkg.installed:
- name: apache2
'''))
with salt.utils.fopen(self.formula_file, 'w') as fp:
fp.write(textwrap.dedent('''\
name: apache
os: RedHat, Debian, Ubuntu, Suse, FreeBSD
os_family: RedHat, Debian, Suse, FreeBSD
version: 201506
release: 2
summary: Formula for installing Apache
description: Formula for installing Apache
'''))
def _spm_config(self):
self._tmp_spm = tempfile.mkdtemp()
config = self.get_temp_config('minion', **{
@ -576,7 +626,7 @@ class SPMCase(TestCase, AdaptedConfigurationTestCaseMixin):
'spm_repos_config': os.path.join(self._tmp_spm, 'etc', 'spm.repos'),
'spm_cache_dir': os.path.join(self._tmp_spm, 'cache'),
'spm_build_dir': os.path.join(self._tmp_spm, 'build'),
'spm_build_exclude': ['.git'],
'spm_build_exclude': ['apache/.git'],
'spm_db_provider': 'sqlite3',
'spm_files_provider': 'local',
'spm_db': os.path.join(self._tmp_spm, 'packages.db'),
@ -595,16 +645,36 @@ class SPMCase(TestCase, AdaptedConfigurationTestCaseMixin):
})
return config
def _spm_create_update_repo(self, config):
build_spm = self.run_spm('build', self.config, self.formula_dir)
c_repo = self.run_spm('create_repo', self.config,
self.config['spm_build_dir'])
repo_conf_dir = self.config['spm_repos_config'] + '.d'
os.makedirs(repo_conf_dir)
import salt.utils
with salt.utils.fopen(os.path.join(repo_conf_dir, 'spm.repo'), 'w') as fp:
fp.write(textwrap.dedent('''\
local_repo:
url: file://{0}
'''.format(self.config['spm_build_dir'])))
u_repo = self.run_spm('update_repo', self.config)
def _spm_client(self, config):
import salt.spm
ui = salt.spm.SPMCmdlineInterface()
client = salt.spm.SPMClient(ui, config)
self.ui = SPMTestUserInterface()
client = salt.spm.SPMClient(self.ui, config)
return client
def run_spm(self, cmd, config, arg=()):
def run_spm(self, cmd, config, arg=None):
client = self._spm_client(config)
spm_cmd = client.run([cmd, arg])
return spm_cmd
return self.ui._status
class ModuleCase(TestCase, SaltClientTestCaseMixin):

View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
'''
Script for copying back xml junit files from tests
'''
from __future__ import absolute_import, print_function
import argparse # pylint: disable=minimum-python-version
import os
import paramiko
import subprocess
import yaml
class DownloadArtifacts(object):
def __init__(self, instance, artifacts):
self.instance = instance
self.artifacts = artifacts
self.client = self.setup_transport()
def setup_transport(self):
# pylint: disable=minimum-python-version
config = yaml.load(subprocess.check_output(['bundle', 'exec', 'kitchen', 'diagnose', self.instance]))
# pylint: enable=minimum-python-version
state = config['instances'][self.instance]['state_file']
tport = config['instances'][self.instance]['transport']
transport = paramiko.Transport((
state['hostname'],
state.get('port', tport.get('port', 22))
))
pkey = paramiko.rsakey.RSAKey(
filename=state.get('ssh_key', tport.get('ssh_key', '~/.ssh/id_rsa'))
)
transport.connect(
username=state.get('username', tport.get('username', 'root')),
pkey=pkey
)
return paramiko.SFTPClient.from_transport(transport)
def download(self):
for remote, local in self.artifacts:
if remote.endswith('/'):
for fxml in self.client.listdir(remote):
self._do_download(os.path.join(remote, fxml), os.path.join(local, os.path.basename(fxml)))
else:
self._do_download(remote, os.path.join(local, os.path.basename(remote)))
def _do_download(self, remote, local):
print('Copying from {0} to {1}'.format(remote, local))
self.client.get(remote, local)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Jenkins Artifact Download Helper')
parser.add_argument(
'--instance',
required=True,
action='store',
help='Instance on Test Kitchen to pull from',
)
parser.add_argument(
'--download-artifacts',
dest='artifacts',
nargs=2,
action='append',
metavar=('REMOTE_PATH', 'LOCAL_PATH'),
help='Download remote artifacts',
)
args = parser.parse_args()
downloader = DownloadArtifacts(args.instance, args.artifacts)
downloader.download()

View File

@ -52,6 +52,7 @@ PYEXEC = 'python{0}.{1}'.format(*sys.version_info)
MOCKBIN = os.path.join(INTEGRATION_TEST_DIR, 'mockbin')
SCRIPT_DIR = os.path.join(CODE_DIR, 'scripts')
TMP_STATE_TREE = os.path.join(SYS_TMP_DIR, 'salt-temp-state-tree')
TMP_PILLAR_TREE = os.path.join(SYS_TMP_DIR, 'salt-temp-pillar-tree')
TMP_PRODENV_STATE_TREE = os.path.join(SYS_TMP_DIR, 'salt-temp-prodenv-state-tree')
TMP_CONF_DIR = os.path.join(TMP, 'config')
TMP_SUB_MINION_CONF_DIR = os.path.join(TMP_CONF_DIR, 'sub-minion')

View File

@ -215,6 +215,7 @@ RUNTIME_VARS = RuntimeVars(
TMP_SYNDIC_MINION_CONF_DIR=paths.TMP_SYNDIC_MINION_CONF_DIR,
TMP_SCRIPT_DIR=paths.TMP_SCRIPT_DIR,
TMP_STATE_TREE=paths.TMP_STATE_TREE,
TMP_PILLAR_TREE=paths.TMP_PILLAR_TREE,
TMP_PRODENV_STATE_TREE=paths.TMP_PRODENV_STATE_TREE,
RUNNING_TESTS_USER=RUNNING_TESTS_USER,
RUNTIME_CONFIGS={}

View File

@ -10,6 +10,8 @@ from __future__ import absolute_import
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
Mock,
patch,
NO_MOCK,
NO_MOCK_REASON
@ -17,6 +19,7 @@ from tests.support.mock import (
# Import Salt Libs
import salt.modules.win_groupadd as win_groupadd
import salt.utils.win_functions
# Import Other Libs
# pylint: disable=unused-import
@ -24,12 +27,46 @@ try:
import win32com
import pythoncom
import pywintypes
PYWINTYPES_ERROR = pywintypes.com_error(
-1234, 'Exception occurred.', (0, None, 'C', None, 0, -4321), None)
HAS_WIN_LIBS = True
except ImportError:
HAS_WIN_LIBS = False
# pylint: enable=unused-import
class MockMember(object):
def __init__(self, name):
self.ADSPath = name
class MockGroupObj(object):
def __init__(self, ads_name, ads_users):
self._members = [MockMember(x) for x in ads_users]
self.Name = ads_name
def members(self):
return self._members
def Add(self, name):
'''
This should be a no-op unless we want to test raising an error, in
which case this should be overridden in a subclass.
'''
pass
def Remove(self, name):
'''
This should be a no-op unless we want to test raising an error, in
which case this should be overridden in a subclass.
'''
pass
if not NO_MOCK:
sam_mock = MagicMock(side_effect=lambda x: 'HOST\\' + x)
@skipIf(not HAS_WIN_LIBS, 'win_groupadd unit tests can only be run if win32com, pythoncom, and pywintypes are installed')
@skipIf(NO_MOCK, NO_MOCK_REASON)
class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
@ -41,106 +78,352 @@ class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
win_groupadd: {'__opts__': {'test': False}}
}
# 'add' function tests: 1
def test_add(self):
'''
Test if it add the specified group
Test adding a new group
'''
self.assertDictEqual(win_groupadd.add('foo'),
{'changes': [], 'name': 'foo', 'result': None,
'comment': 'The group foo already exists.'})
info = MagicMock(return_value=False)
with patch.object(win_groupadd, 'info', info),\
patch.object(win_groupadd, '_get_computer_object', Mock()):
self.assertDictEqual(win_groupadd.add('foo'),
{'changes': ['Successfully created group foo'],
'name': 'foo',
'result': True,
'comment': ''})
# 'delete' function tests: 1
def test_add_group_exists(self):
'''
Test adding a new group if the group already exists
'''
info = MagicMock(return_value={'name': 'foo',
'passwd': None,
'gid': None,
'members': ['HOST\\spongebob']})
with patch.object(win_groupadd, 'info', info),\
patch.object(win_groupadd, '_get_computer_object', Mock()):
self.assertDictEqual(win_groupadd.add('foo'),
{'changes': [], 'name': 'foo', 'result': None,
'comment': 'The group foo already exists.'})
def test_add_error(self):
'''
Test adding a group and encountering an error
'''
class CompObj(object):
def Create(self, type, name):
raise PYWINTYPES_ERROR
compobj_mock = MagicMock(return_value=CompObj())
info = MagicMock(return_value=False)
with patch.object(win_groupadd, 'info', info),\
patch.object(win_groupadd, '_get_computer_object', compobj_mock):
self.assertDictEqual(win_groupadd.add('foo'),
{'changes': [],
'name': 'foo',
'result': False,
'comment': 'Failed to create group foo. C'})
def test_delete(self):
'''
Test if it remove the specified group
Test removing a group
'''
self.assertDictEqual(win_groupadd.delete('foo'),
{'changes': [], 'name': 'foo', 'result': None,
'comment': 'The group foo does not exists.'})
info = MagicMock(return_value={'name': 'foo',
'passwd': None,
'gid': None,
'members': ['HOST\\spongebob']})
with patch.object(win_groupadd, 'info', info), \
patch.object(win_groupadd, '_get_computer_object', Mock()):
self.assertDictEqual(
win_groupadd.delete('foo'),
{'changes': ['Successfully removed group foo'],
'name': 'foo',
'result': True,
'comment': ''})
# 'info' function tests: 1
def test_delete_no_group(self):
'''
Test removing a group that doesn't exists
'''
info = MagicMock(return_value=False)
with patch.object(win_groupadd, 'info', info), \
patch.object(win_groupadd, '_get_computer_object', Mock()):
self.assertDictEqual(win_groupadd.delete('foo'),
{'changes': [], 'name': 'foo', 'result': None,
'comment': 'The group foo does not exists.'})
def test_delete_error(self):
'''
Test removing a group and encountering an error
'''
class CompObj(object):
def Delete(self, type, name):
raise PYWINTYPES_ERROR
compobj_mock = MagicMock(return_value=CompObj())
info = MagicMock(return_value={'name': 'foo',
'passwd': None,
'gid': None,
'members': ['HOST\\spongebob']})
with patch.object(win_groupadd, 'info', info),\
patch.object(win_groupadd, '_get_computer_object', compobj_mock):
self.assertDictEqual(
win_groupadd.delete('foo'),
{'changes': [],
'name': 'foo',
'result': False,
'comment': 'Failed to remove group foo. C'})
def test_info(self):
'''
Test if it return information about a group.
'''
with patch(win_groupadd.win32.client, 'flag', None):
self.assertDictEqual(win_groupadd.info('dc=salt'),
groupobj_mock = MagicMock(return_value=MockGroupObj('salt', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock):
self.assertDictEqual(win_groupadd.info('salt'),
{'gid': None,
'members': ['dc=\\user1'],
'members': ['HOST\\steve'],
'passwd': None,
'name': 'WinNT://./dc=salt,group'})
with patch(win_groupadd.win32.client, 'flag', 1):
self.assertFalse(win_groupadd.info('dc=salt'))
with patch(win_groupadd.win32.client, 'flag', 2):
self.assertFalse(win_groupadd.info('dc=salt'))
# 'getent' function tests: 1
'name': 'salt'})
def test_getent(self):
groupobj_mock = MagicMock(
return_value=[
MockGroupObj('salt', ['WinNT://HOST/steve']),
MockGroupObj('salty', ['WinNT://HOST/spongebob'])])
mock_g_to_g = MagicMock(side_effect=[1, 2])
with patch.object(win_groupadd, '_get_all_groups', groupobj_mock),\
patch.dict(win_groupadd.__salt__, {'file.group_to_gid': mock_g_to_g}):
self.assertListEqual(
win_groupadd.getent(),
[
{'gid': 1, 'members': ['HOST\\steve'], 'name': 'salt', 'passwd': 'x'},
{'gid': 2, 'members': ['HOST\\spongebob'], 'name': 'salty', 'passwd': 'x'}
])
def test_getent_context(self):
'''
Test if it return info on all groups
Test group.getent is using the values in __context__
'''
with patch.dict(win_groupadd.__context__, {'group.getent': True}):
self.assertTrue(win_groupadd.getent())
# 'adduser' function tests: 1
def test_adduser(self):
'''
Test if it add a user to a group
Test adding a user to a group
'''
with patch(win_groupadd.win32.client, 'flag', None):
self.assertDictEqual(win_groupadd.adduser('dc=foo', 'dc=\\username'),
{'changes': {'Users Added': ['dc=\\username']},
'comment': '', 'name': 'dc=foo', 'result': True})
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'spongebob'),
{'changes': {'Users Added': ['HOST\\spongebob']},
'comment': '',
'name': 'foo',
'result': True})
with patch(win_groupadd.win32.client, 'flag', 1):
comt = ('Failed to add dc=\\username to group dc=foo. C')
self.assertDictEqual(win_groupadd.adduser('dc=foo', 'dc=\\username'),
{'changes': {'Users Added': []}, 'name': 'dc=foo',
'comment': comt, 'result': False})
def test_adduser_already_exists(self):
'''
Test adding a user that already exists
'''
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'steve'),
{'changes': {'Users Added': []},
'comment': 'User HOST\\steve is already a member of foo',
'name': 'foo',
'result': None})
# 'deluser' function tests: 1
def test_adduser_error(self):
'''
Test adding a user and encountering an error
'''
# Create mock group object with mocked Add function which raises the
# exception we need in order to test the error case.
class GroupObj(MockGroupObj):
def Add(self, name):
raise PYWINTYPES_ERROR
groupobj_mock = MagicMock(return_value=GroupObj('foo', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'username'),
{'changes': {'Users Added': []},
'name': 'foo',
'comment': 'Failed to add HOST\\username to group foo. C',
'result': False})
def test_adduser_group_does_not_exist(self):
groupobj_mock = MagicMock(side_effect=PYWINTYPES_ERROR)
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'spongebob'),
{'changes': {'Users Added': []},
'name': 'foo',
'comment': 'Failure accessing group foo. C',
'result': False})
def test_deluser(self):
'''
Test if it remove a user to a group
Test removing a user from a group
'''
ret = {'changes': {'Users Removed': []},
'comment': 'User dc=\\username is not a member of dc=foo',
'name': 'dc=foo', 'result': None}
# Test removing a user
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', ['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
ret = {'changes': {'Users Removed': ['spongebob']},
'comment': '',
'name': 'foo',
'result': True}
self.assertDictEqual(win_groupadd.deluser('foo', 'spongebob'), ret)
self.assertDictEqual(win_groupadd.deluser('dc=foo', 'dc=\\username'),
ret)
def test_deluser_no_user(self):
'''
Test removing a user from a group and that user is not a member of the
group
'''
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
ret = {'changes': {'Users Removed': []},
'comment': 'User spongebob is not a member of foo',
'name': 'foo',
'result': None}
self.assertDictEqual(win_groupadd.deluser('foo', 'spongebob'), ret)
# 'members' function tests: 1
def test_deluser_error(self):
'''
Test removing a user and encountering an error
'''
class GroupObj(MockGroupObj):
def Remove(self, name):
raise PYWINTYPES_ERROR
groupobj_mock = MagicMock(return_value=GroupObj('foo', ['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.deluser('foo', 'spongebob'),
{'changes': {'Users Removed': []},
'name': 'foo',
'comment': 'Failed to remove spongebob from group foo. C',
'result': False})
def test_deluser_group_does_not_exist(self):
groupobj_mock = MagicMock(side_effect=PYWINTYPES_ERROR)
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.deluser('foo', 'spongebob'),
{'changes': {'Users Removed': []},
'name': 'foo',
'comment': 'Failure accessing group foo. C',
'result': False})
def test_members(self):
'''
Test if it remove a user to a group
Test adding a list of members to a group, all existing users removed
'''
comment = ['Failure accessing group dc=foo. C']
ret = {'name': 'dc=foo', 'result': False, 'comment': comment,
'changes': {'Users Added': [], 'Users Removed': []}}
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', ['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.members('foo', 'spongebob,patrick,squidward'),
{'changes': {
'Users Added': ['HOST\\patrick', 'HOST\\spongebob', 'HOST\\squidward'],
'Users Removed': ['HOST\\steve']
},
'comment': [],
'name': 'foo',
'result': True})
with patch(win_groupadd.win32.client, 'flag', 2):
self.assertDictEqual(win_groupadd.members
('dc=foo', 'dc=\\user1,dc=\\user2,dc=\\user3'),
ret)
def test_members_correct_membership(self):
'''
Test adding a list of users where the list of users already exists
'''
members_list = ['WinNT://HOST/spongebob',
'WinNT://HOST/squidward',
'WinNT://HOST/patrick']
groupobj_mock = MagicMock(return_value=MockGroupObj('foo', members_list))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.members('foo', 'spongebob,patrick,squidward'),
{'changes': {'Users Added': [], 'Users Removed': []},
'comment': ['foo membership is correct'],
'name': 'foo',
'result': None})
with patch(win_groupadd.win32.client, 'flag', 1):
comment = ['Failed to add dc=\\user2 to dc=foo. C',
'Failed to remove dc=\\user1 from dc=foo. C']
ret.update({'comment': comment, 'result': False})
self.assertDictEqual(win_groupadd.members('dc=foo', 'dc=\\user2'), ret)
def test_members_group_does_not_exist(self):
'''
Test adding a list of users where the group does not exist
'''
groupobj_mock = MagicMock(side_effect=PYWINTYPES_ERROR)
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.members('foo', 'spongebob'),
{'changes': {'Users Added': [], 'Users Removed': []},
'comment': ['Failure accessing group foo. C'],
'name': 'foo',
'result': False})
with patch(win_groupadd.win32.client, 'flag', None):
comment = ['dc=foo membership is correct']
ret.update({'comment': comment, 'result': None})
self.assertDictEqual(win_groupadd.members('dc=foo', 'dc=\\user1'), ret)
def test_members_fail_to_remove(self):
'''
Test adding a list of members and fail to remove members not in the list
'''
class GroupObj(MockGroupObj):
def Remove(self, name):
raise PYWINTYPES_ERROR
groupobj_mock = MagicMock(return_value=GroupObj('foo', ['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.members('foo', 'patrick'),
{'changes': {'Users Added': ['HOST\\patrick'], 'Users Removed': []},
'comment': ['Failed to remove HOST\\spongebob from foo. C'],
'name': 'foo',
'result': False})
def test_members_fail_to_add(self):
'''
Test adding a list of members and failing to add
'''
class GroupObj(MockGroupObj):
def Add(self, name):
raise PYWINTYPES_ERROR
groupobj_mock = MagicMock(return_value=GroupObj('foo', ['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.members('foo', 'patrick'),
{'changes': {'Users Added': [], 'Users Removed': ['HOST\\spongebob']},
'comment': ['Failed to add HOST\\patrick to foo. C'],
'name': 'foo',
'result': False})
def test_list_groups(self):
'''
Test that list groups returns a list of groups by name
'''
groupobj_mock = MagicMock(
return_value=[
MockGroupObj('salt', ['WinNT://HOST/steve']),
MockGroupObj('salty', ['WinNT://HOST/Administrator'])])
with patch.object(win_groupadd, '_get_all_groups', groupobj_mock):
self.assertListEqual(win_groupadd.list_groups(),
['salt', 'salty'])
def test_list_groups_context(self):
'''
Test group.list_groups is using the values in __context__
'''
with patch.dict(win_groupadd.__context__, {'group.list_groups': True}):
self.assertTrue(win_groupadd.list_groups())

View File

@ -814,7 +814,8 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
ret.update({
'comment': comt,
'result': None,
'pchanges': p_chg
'pchanges': p_chg,
'changes': {'/etc/grub.conf': {'directory': 'new'}}
})
self.assertDictEqual(filestate.directory(name,
user=user,
@ -825,7 +826,7 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
with patch.object(os.path, 'isdir', mock_f):
comt = ('No directory to create {0} in'
.format(name))
ret.update({'comment': comt, 'result': False})
ret.update({'comment': comt, 'result': False, 'changes': {}})
self.assertDictEqual(filestate.directory
(name, user=user, group=group),
ret)

View File

@ -313,21 +313,21 @@ class PyDSLRendererTestCase(CommonTestCaseBoilerplate):
- cwd: /
.Y:
cmd.run:
- name: echo Y >> {1}
- name: echo Y >> {0}
- cwd: /
.Z:
cmd.run:
- name: echo Z >> {2}
- name: echo Z >> {0}
- cwd: /
'''.format(output, output, output)))
'''.format(output.replace('\\', '/'))))
write_to(os.path.join(dirpath, 'yyy.sls'), textwrap.dedent('''\
#!pydsl|stateconf -ps
__pydsl__.set(ordered=True)
state('.D').cmd.run('echo D >> {0}', cwd='/')
state('.E').cmd.run('echo E >> {1}', cwd='/')
state('.F').cmd.run('echo F >> {2}', cwd='/')
'''.format(output, output, output)))
state('.E').cmd.run('echo E >> {0}', cwd='/')
state('.F').cmd.run('echo F >> {0}', cwd='/')
'''.format(output.replace('\\', '/'))))
write_to(os.path.join(dirpath, 'aaa.sls'), textwrap.dedent('''\
#!pydsl|stateconf -ps
@ -343,9 +343,9 @@ class PyDSLRendererTestCase(CommonTestCaseBoilerplate):
__pydsl__.set(ordered=True)
state('.A').cmd.run('echo A >> {0}', cwd='/')
state('.B').cmd.run('echo B >> {1}', cwd='/')
state('.C').cmd.run('echo C >> {2}', cwd='/')
'''.format(output, output, output)))
state('.B').cmd.run('echo B >> {0}', cwd='/')
state('.C').cmd.run('echo C >> {0}', cwd='/')
'''.format(output.replace('\\', '/'))))
self.state_highstate({'base': ['aaa']}, dirpath)
with salt.utils.fopen(output, 'r') as f:
@ -365,26 +365,29 @@ class PyDSLRendererTestCase(CommonTestCaseBoilerplate):
)
)
try:
# The Windows shell will include any spaces before the redirect
# in the text that is redirected.
# For example: echo hello > test.txt will contain "hello "
write_to(os.path.join(dirpath, 'aaa.sls'), textwrap.dedent('''\
#!pydsl
__pydsl__.set(ordered=True)
A = state('A')
A.cmd.run('echo hehe > {0}/zzz.txt', cwd='/')
A.file.managed('{1}/yyy.txt', source='salt://zzz.txt')
A.cmd.run('echo hehe>{0}/zzz.txt', cwd='/')
A.file.managed('{0}/yyy.txt', source='salt://zzz.txt')
A()
A()
state().cmd.run('echo hoho >> {2}/yyy.txt', cwd='/')
state().cmd.run('echo hoho>>{0}/yyy.txt', cwd='/')
A.file.managed('{3}/xxx.txt', source='salt://zzz.txt')
A.file.managed('{0}/xxx.txt', source='salt://zzz.txt')
A()
'''.format(dirpath, dirpath, dirpath, dirpath)))
'''.format(dirpath.replace('\\', '/'))))
self.state_highstate({'base': ['aaa']}, dirpath)
with salt.utils.fopen(os.path.join(dirpath, 'yyy.txt'), 'rt') as f:
self.assertEqual(f.read(), 'hehe\nhoho\n')
self.assertEqual(f.read(), 'hehe' + os.linesep + 'hoho' + os.linesep)
with salt.utils.fopen(os.path.join(dirpath, 'xxx.txt'), 'rt') as f:
self.assertEqual(f.read(), 'hehe\n')
self.assertEqual(f.read(), 'hehe' + os.linesep)
finally:
shutil.rmtree(dirpath, ignore_errors=True)