How to use recwarn method in Pytest

Best Python code snippet using pytest

test_config.py

Source:test_config.py Github

copy

Full Screen

1import mock2import pytest3from sqlalchemy.pool import NullPool4import flask_sqlalchemy as fsa5from flask_sqlalchemy import _compat, utils6@pytest.fixture7def app_nr(app):8 """9 Signal/event registration with record queries breaks when10 sqlalchemy.create_engine() is mocked out.11 """12 app.config['SQLALCHEMY_RECORD_QUERIES'] = False13 return app14class TestConfigKeys:15 def test_defaults(self, app, recwarn):16 """17 Test all documented config values in the order they appear in our18 documentation: http://flask-sqlalchemy.pocoo.org/dev/config/19 """20 fsa.SQLAlchemy(app)21 # Expecting no warnings for default config22 assert len(recwarn) == 023 assert app.config['SQLALCHEMY_DATABASE_URI'] == 'sqlite:///:memory:'24 assert app.config['SQLALCHEMY_BINDS'] is None25 assert app.config['SQLALCHEMY_ECHO'] is False26 assert app.config['SQLALCHEMY_RECORD_QUERIES'] is None27 assert app.config['SQLALCHEMY_NATIVE_UNICODE'] is None28 assert app.config['SQLALCHEMY_POOL_SIZE'] is None29 assert app.config['SQLALCHEMY_POOL_TIMEOUT'] is None30 assert app.config['SQLALCHEMY_POOL_RECYCLE'] is None31 assert app.config['SQLALCHEMY_MAX_OVERFLOW'] is None32 assert app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] is False33 assert app.config['SQLALCHEMY_ENGINE_OPTIONS'] == {}34 def test_uri_binds_warning(self, app, recwarn):35 # Let's trigger the warning36 del app.config['SQLALCHEMY_DATABASE_URI']37 fsa.SQLAlchemy(app)38 # and verify it showed up as expected39 assert len(recwarn) == 140 expect = 'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS' \41 ' is set. Defaulting SQLALCHEMY_DATABASE_URI to'\42 ' "sqlite:///:memory:".'43 assert recwarn[0].message.args[0] == expect44 def test_engine_creation_ok(self, app, recwarn):45 """ create_engine() isn't called until needed. Let's make sure we can do that without46 errors or warnings.47 """48 assert fsa.SQLAlchemy(app).get_engine()49 if utils.sqlalchemy_version('==', '0.8.0') and not _compat.PY2:50 # In CI, we test Python 3.6 and SA 0.8.0, which produces a warning for51 # inspect.getargspec()52 expected_warnings = 153 else:54 expected_warnings = 055 assert len(recwarn) == expected_warnings56 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)57 def test_native_unicode_deprecation_config_opt(self, m_create_engine, app_nr, recwarn):58 app_nr.config['SQLALCHEMY_NATIVE_UNICODE'] = False59 assert fsa.SQLAlchemy(app_nr).get_engine()60 assert len(recwarn) == 161 warning_msg = recwarn[0].message.args[0]62 assert 'SQLALCHEMY_NATIVE_UNICODE' in warning_msg63 assert 'deprecated and will be removed in v3.0' in warning_msg64 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)65 def test_native_unicode_deprecation_init_opt(self, m_create_engine, app_nr, recwarn):66 assert fsa.SQLAlchemy(app_nr, use_native_unicode=False).get_engine()67 assert len(recwarn) == 168 warning_msg = recwarn[0].message.args[0]69 assert 'use_native_unicode' in warning_msg70 assert 'deprecated and will be removed in v3.0' in warning_msg71 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)72 def test_deprecation_config_opt_pool_size(self, m_create_engine, app_nr, recwarn):73 app_nr.config['SQLALCHEMY_POOL_SIZE'] = 574 assert fsa.SQLAlchemy(app_nr).get_engine()75 assert len(recwarn) == 176 warning_msg = recwarn[0].message.args[0]77 assert 'SQLALCHEMY_POOL_SIZE' in warning_msg78 assert 'deprecated and will be removed in v3.0.' in warning_msg79 assert 'pool_size' in warning_msg80 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)81 def test_deprecation_config_opt_pool_timeout(self, m_create_engine, app_nr, recwarn):82 app_nr.config['SQLALCHEMY_POOL_TIMEOUT'] = 583 assert fsa.SQLAlchemy(app_nr).get_engine()84 assert len(recwarn) == 185 warning_msg = recwarn[0].message.args[0]86 assert 'SQLALCHEMY_POOL_TIMEOUT' in warning_msg87 assert 'deprecated and will be removed in v3.0.' in warning_msg88 assert 'pool_timeout' in warning_msg89 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)90 def test_deprecation_config_opt_pool_recycle(self, m_create_engine, app_nr, recwarn):91 app_nr.config['SQLALCHEMY_POOL_RECYCLE'] = 592 assert fsa.SQLAlchemy(app_nr).get_engine()93 assert len(recwarn) == 194 warning_msg = recwarn[0].message.args[0]95 assert 'SQLALCHEMY_POOL_RECYCLE' in warning_msg96 assert 'deprecated and will be removed in v3.0.' in warning_msg97 assert 'pool_recycle' in warning_msg98 @mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)99 def test_deprecation_config_opt_max_overflow(self, m_create_engine, app_nr, recwarn):100 app_nr.config['SQLALCHEMY_MAX_OVERFLOW'] = 5101 assert fsa.SQLAlchemy(app_nr).get_engine()102 assert len(recwarn) == 1103 warning_msg = recwarn[0].message.args[0]104 assert 'SQLALCHEMY_MAX_OVERFLOW' in warning_msg105 assert 'deprecated and will be removed in v3.0.' in warning_msg106 assert 'max_overflow' in warning_msg107@mock.patch.object(fsa.sqlalchemy, 'create_engine', autospec=True, spec_set=True)108class TestCreateEngine:109 """110 Tests for _EngineConnector and SQLAlchemy methods inolved in setting up111 the SQLAlchemy engine.112 """113 def test_engine_echo_default(self, m_create_engine, app_nr):114 fsa.SQLAlchemy(app_nr).get_engine()115 args, options = m_create_engine.call_args116 assert 'echo' not in options117 def test_engine_echo_true(self, m_create_engine, app_nr):118 app_nr.config['SQLALCHEMY_ECHO'] = True119 fsa.SQLAlchemy(app_nr).get_engine()120 args, options = m_create_engine.call_args121 assert options['echo'] is True122 @mock.patch.object(fsa.utils, 'sqlalchemy')123 def test_convert_unicode_default_sa_13(self, m_sqlalchemy, m_create_engine, app_nr):124 m_sqlalchemy.__version__ = '1.3'125 fsa.SQLAlchemy(app_nr).get_engine()126 args, options = m_create_engine.call_args127 assert 'convert_unicode' not in options128 def test_config_from_engine_options(self, m_create_engine, app_nr):129 app_nr.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'foo': 'bar'}130 fsa.SQLAlchemy(app_nr).get_engine()131 args, options = m_create_engine.call_args132 assert options['foo'] == 'bar'133 def test_config_from_init(self, m_create_engine, app_nr):134 fsa.SQLAlchemy(app_nr, engine_options={'bar': 'baz'}).get_engine()135 args, options = m_create_engine.call_args136 assert options['bar'] == 'baz'137 def test_pool_class_default(self, m_create_engine, app_nr):138 fsa.SQLAlchemy(app_nr).get_engine()139 args, options = m_create_engine.call_args140 assert options['poolclass'].__name__ == 'StaticPool'141 def test_pool_class_with_pool_size_zero(self, m_create_engine, app_nr, recwarn):142 app_nr.config['SQLALCHEMY_POOL_SIZE'] = 0143 with pytest.raises(RuntimeError) as exc_info:144 fsa.SQLAlchemy(app_nr).get_engine()145 expected = 'SQLite in memory database with an empty queue not possible due to data loss.'146 assert exc_info.value.args[0] == expected147 def test_pool_class_nullpool(self, m_create_engine, app_nr):148 engine_options = {'poolclass': NullPool}149 fsa.SQLAlchemy(app_nr, engine_options=engine_options).get_engine()150 args, options = m_create_engine.call_args151 assert options['poolclass'].__name__ == 'NullPool'...

Full Screen

Full Screen

test_deprecate.py

Source:test_deprecate.py Github

copy

Full Screen

1import pytest2import inspect3import warnings4from .._deprecate import (5 TrioDeprecationWarning,6 warn_deprecated,7 deprecated,8 deprecated_alias,9)10from . import module_with_deprecations11@pytest.fixture12def recwarn_always(recwarn):13 warnings.simplefilter("always")14 # ResourceWarnings about unclosed sockets can occur nondeterministically15 # (during GC) which throws off the tests in this file16 warnings.simplefilter("ignore", ResourceWarning)17 return recwarn18def _here():19 info = inspect.getframeinfo(inspect.currentframe().f_back)20 return (info.filename, info.lineno)21def test_warn_deprecated(recwarn_always):22 def deprecated_thing():23 warn_deprecated("ice", "1.2", issue=1, instead="water")24 deprecated_thing()25 filename, lineno = _here()26 assert len(recwarn_always) == 127 got = recwarn_always.pop(TrioDeprecationWarning)28 assert "ice is deprecated" in got.message.args[0]29 assert "Trio 1.2" in got.message.args[0]30 assert "water instead" in got.message.args[0]31 assert "/issues/1" in got.message.args[0]32 assert got.filename == filename33 assert got.lineno == lineno - 134def test_warn_deprecated_no_instead_or_issue(recwarn_always):35 # Explicitly no instead or issue36 warn_deprecated("water", "1.3", issue=None, instead=None)37 assert len(recwarn_always) == 138 got = recwarn_always.pop(TrioDeprecationWarning)39 assert "water is deprecated" in got.message.args[0]40 assert "no replacement" in got.message.args[0]41 assert "Trio 1.3" in got.message.args[0]42def test_warn_deprecated_stacklevel(recwarn_always):43 def nested1():44 nested2()45 def nested2():46 warn_deprecated("x", "1.3", issue=7, instead="y", stacklevel=3)47 filename, lineno = _here()48 nested1()49 got = recwarn_always.pop(TrioDeprecationWarning)50 assert got.filename == filename51 assert got.lineno == lineno + 152def old(): # pragma: no cover53 pass54def new(): # pragma: no cover55 pass56def test_warn_deprecated_formatting(recwarn_always):57 warn_deprecated(old, "1.0", issue=1, instead=new)58 got = recwarn_always.pop(TrioDeprecationWarning)59 assert "test_deprecate.old is deprecated" in got.message.args[0]60 assert "test_deprecate.new instead" in got.message.args[0]61@deprecated("1.5", issue=123, instead=new)62def deprecated_old():63 return 364def test_deprecated_decorator(recwarn_always):65 assert deprecated_old() == 366 got = recwarn_always.pop(TrioDeprecationWarning)67 assert "test_deprecate.deprecated_old is deprecated" in got.message.args[0]68 assert "1.5" in got.message.args[0]69 assert "test_deprecate.new" in got.message.args[0]70 assert "issues/123" in got.message.args[0]71class Foo:72 @deprecated("1.0", issue=123, instead="crying")73 def method(self):74 return 775def test_deprecated_decorator_method(recwarn_always):76 f = Foo()77 assert f.method() == 778 got = recwarn_always.pop(TrioDeprecationWarning)79 assert "test_deprecate.Foo.method is deprecated" in got.message.args[0]80@deprecated("1.2", thing="the thing", issue=None, instead=None)81def deprecated_with_thing():82 return 7283def test_deprecated_decorator_with_explicit_thing(recwarn_always):84 assert deprecated_with_thing() == 7285 got = recwarn_always.pop(TrioDeprecationWarning)86 assert "the thing is deprecated" in got.message.args[0]87def new_hotness():88 return "new hotness"89old_hotness = deprecated_alias("old_hotness", new_hotness, "1.23", issue=1)90def test_deprecated_alias(recwarn_always):91 assert old_hotness() == "new hotness"92 got = recwarn_always.pop(TrioDeprecationWarning)93 assert "test_deprecate.old_hotness is deprecated" in got.message.args[0]94 assert "1.23" in got.message.args[0]95 assert "test_deprecate.new_hotness instead" in got.message.args[0]96 assert "issues/1" in got.message.args[0]97 assert ".. deprecated:: 1.23" in old_hotness.__doc__98 assert "test_deprecate.new_hotness instead" in old_hotness.__doc__99 assert "issues/1>`__" in old_hotness.__doc__100class Alias:101 def new_hotness_method(self):102 return "new hotness method"103 old_hotness_method = deprecated_alias(104 "Alias.old_hotness_method", new_hotness_method, "3.21", issue=1105 )106def test_deprecated_alias_method(recwarn_always):107 obj = Alias()108 assert obj.old_hotness_method() == "new hotness method"109 got = recwarn_always.pop(TrioDeprecationWarning)110 msg = got.message.args[0]111 assert "test_deprecate.Alias.old_hotness_method is deprecated" in msg112 assert "test_deprecate.Alias.new_hotness_method instead" in msg113@deprecated("2.1", issue=1, instead="hi")114def docstring_test1(): # pragma: no cover115 """Hello!"""116@deprecated("2.1", issue=None, instead="hi")117def docstring_test2(): # pragma: no cover118 """Hello!"""119@deprecated("2.1", issue=1, instead=None)120def docstring_test3(): # pragma: no cover121 """Hello!"""122@deprecated("2.1", issue=None, instead=None)123def docstring_test4(): # pragma: no cover124 """Hello!"""125def test_deprecated_docstring_munging():126 assert (127 docstring_test1.__doc__128 == """Hello!129.. deprecated:: 2.1130 Use hi instead.131 For details, see `issue #1 <https://github.com/python-trio/trio/issues/1>`__.132"""133 )134 assert (135 docstring_test2.__doc__136 == """Hello!137.. deprecated:: 2.1138 Use hi instead.139"""140 )141 assert (142 docstring_test3.__doc__143 == """Hello!144.. deprecated:: 2.1145 For details, see `issue #1 <https://github.com/python-trio/trio/issues/1>`__.146"""147 )148 assert (149 docstring_test4.__doc__150 == """Hello!151.. deprecated:: 2.1152"""153 )154def test_module_with_deprecations(recwarn_always):155 assert module_with_deprecations.regular == "hi"156 assert len(recwarn_always) == 0157 filename, lineno = _here()158 assert module_with_deprecations.dep1 == "value1"159 got = recwarn_always.pop(TrioDeprecationWarning)160 assert got.filename == filename161 assert got.lineno == lineno + 1162 assert "module_with_deprecations.dep1" in got.message.args[0]163 assert "Trio 1.1" in got.message.args[0]164 assert "/issues/1" in got.message.args[0]165 assert "value1 instead" in got.message.args[0]166 assert module_with_deprecations.dep2 == "value2"167 got = recwarn_always.pop(TrioDeprecationWarning)168 assert "instead-string instead" in got.message.args[0]169 with pytest.raises(AttributeError):...

Full Screen

Full Screen

test_utils.py

Source:test_utils.py Github

copy

Full Screen

1# MIT License2#3# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 20204#5# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated6# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the7# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit8# persons to whom the Software is furnished to do so, subject to the following conditions:9#10# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the11# Software.12#13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE14# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,16# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE17# SOFTWARE.18from __future__ import absolute_import, division, print_function, unicode_literals19import logging20import pytest21from art.utils import Deprecated, deprecated, deprecated_keyword_arg22logger = logging.getLogger(__name__)23class TestDeprecated:24 """25 Test the deprecation decorator functions and methods.26 """27 def test_deprecated_simple(self):28 @deprecated("1.3.0")29 def simple_addition(a, b):30 return a + b31 with pytest.deprecated_call():32 simple_addition(1, 2)33 def test_deprecated_reason_keyword(self, recwarn):34 @deprecated("1.3.0", reason="With some reason message.")35 def simple_addition(a, b):36 return a + b37 warn_msg_expected = (38 "Function 'simple_addition' is deprecated and will be removed in future release 1.3.0."39 "\nWith some reason message."40 )41 simple_addition(1, 2)42 warn_obj = recwarn.pop(DeprecationWarning)43 assert str(warn_obj.message) == warn_msg_expected44 def test_deprecated_replaced_by_keyword(self, recwarn):45 @deprecated("1.3.0", replaced_by="sum")46 def simple_addition(a, b):47 return a + b48 warn_msg_expected = (49 "Function 'simple_addition' is deprecated and will be removed in future release 1.3.0."50 " It will be replaced by 'sum'."51 )52 simple_addition(1, 2)53 warn_obj = recwarn.pop(DeprecationWarning)54 assert str(warn_obj.message) == warn_msg_expected55class TestDeprecatedKeyword:56 """57 Test the deprecation decorator for keyword arguments.58 """59 def test_deprecated_keyword_used(self):60 @deprecated_keyword_arg("a", "1.3.0")61 def simple_addition(a=Deprecated, b=1):62 result = a if a is Deprecated else a + b63 return result64 with pytest.deprecated_call():65 simple_addition(a=1)66 def test_deprecated_keyword_not_used(self, recwarn):67 @deprecated_keyword_arg("b", "1.3.0")68 def simple_addition(a, b=Deprecated):69 result = a if b is Deprecated else a + b70 return result71 simple_addition(1)72 assert len(recwarn) == 073 def test_reason(self, recwarn):74 @deprecated_keyword_arg("a", "1.3.0", reason="With some reason message.")75 def simple_addition(a=Deprecated, b=1):76 result = a if a is Deprecated else a + b77 return result78 warn_msg_expected = (79 "Keyword argument 'a' in 'simple_addition' is deprecated and will be removed in future release 1.3.0."80 "\nWith some reason message."81 )82 simple_addition(a=1)83 warn_obj = recwarn.pop(DeprecationWarning)84 assert str(warn_obj.message) == warn_msg_expected85 def test_replaced_by(self, recwarn):86 @deprecated_keyword_arg("b", "1.3.0", replaced_by="c")87 def simple_addition(a=1, b=Deprecated, c=1):88 result = a + c if b is Deprecated else a + b89 return result90 warn_msg_expected = (91 "Keyword argument 'b' in 'simple_addition' is deprecated and will be removed in future release 1.3.0."92 " It will be replaced by 'c'."93 )94 simple_addition(a=1, b=1)95 warn_obj = recwarn.pop(DeprecationWarning)96 assert str(warn_obj.message) == warn_msg_expected97 def test_replaced_by_keyword_missing_signature_error(self, recwarn):98 @deprecated_keyword_arg("b", "1.3.0", replaced_by="c")99 def simple_addition(a=1, b=Deprecated):100 result = a if b is Deprecated else a + b101 return result102 exc_msg = "Deprecated keyword replacement not found in function signature."103 with pytest.raises(ValueError, match=exc_msg):104 simple_addition(a=1)105 def test_deprecated_keyword_default_value_error(self):106 @deprecated_keyword_arg("a", "1.3.0")107 def simple_addition(a=None, b=1):108 result = a if a is None else a + b109 return result110 exc_msg = "Deprecated keyword argument must default to the Decorator singleton."111 with pytest.raises(ValueError, match=exc_msg):...

Full Screen

Full Screen

test_deprecation.py

Source:test_deprecation.py Github

copy

Full Screen

1import warnings2import pytest3from flaskbb.deprecation import RemovedInFlaskBB3, deprecated4NEXT_VERSION_STRING = ".".join([str(x) for x in RemovedInFlaskBB3.version])5@deprecated("This is only a drill")6def only_a_drill():7 pass8# TODO(anr): Make the parens optional9@deprecated()10def default_deprecation():11 """12 Existing docstring13 """14 pass15class TestDeprecation(object):16 def test_emits_default_deprecation_warning(self, recwarn):17 warnings.simplefilter("default", RemovedInFlaskBB3)18 default_deprecation()19 assert len(recwarn) == 120 assert "default_deprecation is deprecated" in str(recwarn[0].message)21 assert recwarn[0].category == RemovedInFlaskBB322 assert recwarn[0].filename == __file__23 # assert on the next line is conditional on the position of the call24 # to default_deprecation please don't jiggle it around too much25 assert recwarn[0].lineno == 2826 assert "only_a_drill is deprecated" in only_a_drill.__doc__27 def tests_emits_specialized_message(self, recwarn):28 warnings.simplefilter("default", RemovedInFlaskBB3)29 only_a_drill()30 expected = "only_a_drill is deprecated and will be removed in version {}. This is only a drill".format( # noqa31 NEXT_VERSION_STRING32 )33 assert len(recwarn) == 134 assert expected in str(recwarn[0].message)35 def tests_only_accepts_FlaskBBDeprecationWarnings(self):36 with pytest.raises(ValueError) as excinfo:37 # DeprecationWarning is ignored by default38 @deprecated("This is also a drill", category=UserWarning)39 def also_a_drill():40 pass41 assert "Expected subclass of FlaskBBDeprecation" in str(excinfo.value)42 def tests_deprecated_decorator_work_with_method(self, recwarn):43 warnings.simplefilter("default", RemovedInFlaskBB3)44 self.deprecated_instance_method()45 assert len(recwarn) == 146 def test_adds_to_existing_docstring(self, recwarn):47 docstring = default_deprecation.__doc__48 assert "Existing docstring" in docstring49 assert "default_deprecation is deprecated" in docstring50 @deprecated()51 def deprecated_instance_method(self):...

Full Screen

Full Screen

Pytest Tutorial

Looking for an in-depth tutorial around pytest? LambdaTest covers the detailed pytest tutorial that has everything related to the pytest, from setting up the pytest framework to automation testing. Delve deeper into pytest testing by exploring advanced use cases like parallel testing, pytest fixtures, parameterization, executing multiple test cases from a single file, and more.

Chapters

  1. What is pytest
  2. Pytest installation: Want to start pytest from scratch? See how to install and configure pytest for Python automation testing.
  3. Run first test with pytest framework: Follow this step-by-step tutorial to write and run your first pytest script.
  4. Parallel testing with pytest: A hands-on guide to parallel testing with pytest to improve the scalability of your test automation.
  5. Generate pytest reports: Reports make it easier to understand the results of pytest-based test runs. Learn how to generate pytest reports.
  6. Pytest Parameterized tests: Create and run your pytest scripts while avoiding code duplication and increasing test coverage with parameterization.
  7. Pytest Fixtures: Check out how to implement pytest fixtures for your end-to-end testing needs.
  8. Execute Multiple Test Cases: Explore different scenarios for running multiple test cases in pytest from a single file.
  9. Stop Test Suite after N Test Failures: See how to stop your test suite after n test failures in pytest using the @pytest.mark.incremental decorator and maxfail command-line option.

YouTube

Skim our below pytest tutorial playlist to get started with automation testing using the pytest framework.

https://www.youtube.com/playlist?list=PLZMWkkQEwOPlcGgDmHl8KkXKeLF83XlrP

Run Pytest automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful