6 new revisions:

Revision: 271c1b302f10
Branch:   default
Author:   Pekka Klärck
Date:     Wed May 22 16:16:28 2013
Log:      typo fixes in ug
http://code.google.com/p/robotframework/source/detail?r=271c1b302f10

Revision: 314a93cdd952
Branch:   default
Author:   Pekka Klärck
Date:     Thu May 23 23:24:44 2013
Log:      Automated merge with https://code.google.com/p/robotframework/
http://code.google.com/p/robotframework/source/detail?r=314a93cdd952

Revision: d7433e308cc0
Branch:   default
Author:   Pekka Klärck
Date:     Thu May 23 23:32:42 2013
Log:      refactored utests to ease creating new suites into same module
http://code.google.com/p/robotframework/source/detail?r=d7433e308cc0

Revision: b0cb30011848
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 00:45:38 2013
Log:      new run: handle failures in test setup/teardown
http://code.google.com/p/robotframework/source/detail?r=b0cb30011848

Revision: 7ad690543152
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 02:51:16 2013
Log:      Fixed handling using non-existing variables in variable table....
http://code.google.com/p/robotframework/source/detail?r=7ad690543152

Revision: c067ab3b5b43
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 02:51:25 2013
Log:      Automated merge with https://code.google.com/p/robotframework/
http://code.google.com/p/robotframework/source/detail?r=c067ab3b5b43

==============================================================================
Revision: 271c1b302f10
Branch:   default
Author:   Pekka Klärck
Date:     Wed May 22 16:16:28 2013
Log:      typo fixes in ug
http://code.google.com/p/robotframework/source/detail?r=271c1b302f10

Modified:
 /doc/userguide/src/CreatingTestData/CreatingTestCases.rst
 /doc/userguide/src/ExtendingRobotFramework/CreatingTestLibraries.rst

=======================================
--- /doc/userguide/src/CreatingTestData/CreatingTestCases.rst Wed May 8 00:42:06 2013 +++ /doc/userguide/src/CreatingTestData/CreatingTestCases.rst Wed May 22 16:16:28 2013
@@ -255,8 +255,8 @@
 their default values.

 In Robot Framework 2.8 support for using named argument syntax for
-arguments without default values was added. Also possiblity to use named
-argument syntax with Python keywords that take keyworded variable `**kwargs`
+arguments without default values was added. Also possibility to use named
+argument syntax with Python keywords that take keyword arguments `**kwargs`
 was added.


=======================================
--- /doc/userguide/src/ExtendingRobotFramework/CreatingTestLibraries.rst Tue Apr 16 15:01:58 2013 +++ /doc/userguide/src/ExtendingRobotFramework/CreatingTestLibraries.rst Wed May 22 16:16:28 2013
@@ -1291,7 +1291,7 @@

 This is especially important when threads are run on background while
 other keywords are running. Results of communicating with the
-framework in that case are undefined and can in works case cause a
+framework in that case are undefined and can in the worst case cause a
 crash or a corrupted output file. If a keyword starts something on
 background, there should be another keyword that checks the status of
 the worker thread and reports gathered information accordingly.

==============================================================================
Revision: 314a93cdd952
Branch:   default
Author:   Pekka Klärck
Date:     Thu May 23 23:24:44 2013
Log:      Automated merge with https://code.google.com/p/robotframework/
http://code.google.com/p/robotframework/source/detail?r=314a93cdd952



==============================================================================
Revision: d7433e308cc0
Branch:   default
Author:   Pekka Klärck
Date:     Thu May 23 23:32:42 2013
Log:      refactored utests to ease creating new suites into same module
http://code.google.com/p/robotframework/source/detail?r=d7433e308cc0

Modified:
 /utest/new_running/test_running.py

=======================================
--- /utest/new_running/test_running.py  Wed May 22 02:44:45 2013
+++ /utest/new_running/test_running.py  Thu May 23 23:32:42 2013
@@ -5,61 +5,79 @@
 from robot.new_running import TestSuite


+def run(suite):
+    result = suite.run(output='NONE', stdout=StringIO(), stderr=StringIO())
+    return result.suite
+
+
+def assert_suite(suite, name, status, tests=1):
+    assert_equals(suite.name, name)
+    assert_equals(suite.status, status)
+    assert_equals(len(suite.tests), tests)
+
+
+def assert_test(test, name, status, tags=(), msg=''):
+    assert_equals(test.name, name)
+    assert_equals(test.status, status)
+    assert_equals(test.message, msg)
+    assert_equals(tuple(test.tags), tags)
+
+
 class TestRunning(unittest.TestCase):

     def test_one_library_keyword(self):
         suite = TestSuite(name='Suite')
         suite.tests.create(name='Test').keywords.create('Log',
args=['Hello, world!'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'PASS')
-        self._check_test(result.tests[0], 'Test', 'PASS')
+        result = run(suite)
+        assert_suite(result, 'Suite', 'PASS')
+        assert_test(result.tests[0], 'Test', 'PASS')

     def test_failing_library_keyword(self):
         suite = TestSuite(name='Suite')
         test = suite.tests.create(name='Test')
         test.keywords.create('Log', args=['Dont fail yet.'])
         test.keywords.create('Fail', args=['Hello, world!'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'FAIL')
- self._check_test(result.tests[0], 'Test', 'FAIL', msg='Hello, world!')
+        result = run(suite)
+        assert_suite(result, 'Suite', 'FAIL')
+        assert_test(result.tests[0], 'Test', 'FAIL', msg='Hello, world!')

     def test_assign(self):
         suite = TestSuite(name='Suite')
         test = suite.tests.create(name='Test')
test.keywords.create(assign=['${var}'], name='Set Variable', args=['value in variable'])
         test.keywords.create('Fail', args=['${var}'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'FAIL')
- self._check_test(result.tests[0], 'Test', 'FAIL', msg='value in variable')
+        result = run(suite)
+        assert_suite(result, 'Suite', 'FAIL')
+ assert_test(result.tests[0], 'Test', 'FAIL', msg='value in variable')

     def test_suites_in_suites(self):
         root = TestSuite(name='Root')
         root.suites.create(name='Child')\
             .tests.create(name='Test')\
             .keywords.create('Log', args=['Hello, world!'])
-        result = self._run(root)
-        self._check_suite(result, 'Root', 'PASS', tests=0)
-        self._check_suite(result.suites[0], 'Child', 'PASS')
-        self._check_test(result.suites[0].tests[0], 'Test', 'PASS')
+        result = run(root)
+        assert_suite(result, 'Root', 'PASS', tests=0)
+        assert_suite(result.suites[0], 'Child', 'PASS')
+        assert_test(result.suites[0].tests[0], 'Test', 'PASS')

     def test_imports(self):
         suite = TestSuite(name='Suite')
         suite.imports.create('Library', 'OperatingSystem')
suite.tests.create(name='Test').keywords.create('Directory Should Exist',
                                                         args=['.'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'PASS')
-        self._check_test(result.tests[0], 'Test', 'PASS')
+        result = run(suite)
+        assert_suite(result, 'Suite', 'PASS')
+        assert_test(result.tests[0], 'Test', 'PASS')

     def test_user_keywords(self):
         suite = TestSuite(name='Suite')
suite.tests.create(name='Test').keywords.create('User keyword', args=['From uk']) uk = suite.user_keywords.create(name='User keyword', args=['${msg}'])
         uk.keywords.create(name='Fail', args=['${msg}'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'FAIL')
-        self._check_test(result.tests[0], 'Test', 'FAIL', msg='From uk')
+        result = run(suite)
+        assert_suite(result, 'Suite', 'FAIL')
+        assert_test(result.tests[0], 'Test', 'FAIL', msg='From uk')

     def test_variables(self):
         suite = TestSuite(name='Suite')
@@ -67,22 +85,7 @@
         suite.variables.create('@{LIST}', ['Error', 'added tag'])
suite.tests.create(name='T1').keywords.create('Fail', args=['${ERROR}']) suite.tests.create(name='T2').keywords.create('Fail', args=['@{LIST}'])
-        result = self._run(suite)
-        self._check_suite(result, 'Suite', 'FAIL', tests=2)
- self._check_test(result.tests[0], 'T1', 'FAIL', msg='Error message') - self._check_test(result.tests[1], 'T2', 'FAIL', ('added tag',), 'Error')
-
-    def _run(self, suite):
- result = suite.run(output='NONE', stdout=StringIO(), stderr=StringIO())
-        return result.suite
-
-    def _check_suite(self, suite, name, status, tests=1):
-        assert_equals(suite.name, name)
-        assert_equals(suite.status, status)
-        assert_equals(len(suite.tests), tests)
-
-    def _check_test(self, test, name, status, tags=(), msg=''):
-        assert_equals(test.name, name)
-        assert_equals(test.status, status)
-        assert_equals(test.message, msg)
-        assert_equals(tuple(test.tags), tags)
+        result = run(suite)
+        assert_suite(result, 'Suite', 'FAIL', tests=2)
+        assert_test(result.tests[0], 'T1', 'FAIL', msg='Error message')
+        assert_test(result.tests[1], 'T2', 'FAIL', ('added tag',), 'Error')

==============================================================================
Revision: b0cb30011848
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 00:45:38 2013
Log:      new run: handle failures in test setup/teardown
http://code.google.com/p/robotframework/source/detail?r=b0cb30011848

Added:
 /src/robot/new_running/failures.py
Modified:
 /src/robot/new_running/runner.py
 /utest/new_running/test_running.py

=======================================
--- /dev/null
+++ /src/robot/new_running/failures.py  Fri May 24 00:45:38 2013
@@ -0,0 +1,47 @@
+#  Copyright 2008-2012 Nokia Siemens Networks Oyj
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+
+class TestFailures(object):
+
+    def __init__(self):
+        self.setup_failure = None
+        self.test_failure = None
+        self.teardown_failure = None
+
+    @property
+    def run_allowed(self):
+        return self.setup_failure is None
+
+    @property
+    def message(self):
+        msg = self._get_message_before_teardown()
+        return self._get_message_after_teardown(msg)
+
+    def _get_message_before_teardown(self):
+        if self.setup_failure is not None:
+            return 'Setup failed:\n%s' % self.setup_failure
+        return self.test_failure or ''
+
+    def _get_message_after_teardown(self, msg):
+        if self.teardown_failure is None:
+            return msg
+        if not msg:
+            return 'Teardown failed:\n%s' % self.teardown_failure
+ return '%s\n\nAlso teardown failed:\n%s' % (msg, self.teardown_failure)
+
+    def __nonzero__(self):
+        return (self.setup_failure is not None or
+                self.test_failure is not None or
+                self.teardown_failure is not None)
=======================================
--- /src/robot/new_running/runner.py    Wed May 22 15:27:57 2013
+++ /src/robot/new_running/runner.py    Fri May 24 00:45:38 2013
@@ -18,12 +18,14 @@
 from robot.running.namespace import Namespace
 from robot.variables import GLOBAL_VARIABLES
 from robot.running.context import EXECUTION_CONTEXTS
-from robot.running.keywords import Keywords
+from robot.running.keywords import Keywords, Keyword
 from robot.running.fixture import Setup, Teardown
 from robot.running.userkeyword import UserLibrary
-from robot.errors import ExecutionFailed
+from robot.errors import ExecutionFailed, DataError
 from robot import utils

+from .failures import TestFailures
+

 class Runner(SuiteVisitor):

@@ -78,27 +80,49 @@
doc=self._resolve_setting(test.doc),
                                           tags=test.tags,
                                           starttime=utils.get_timestamp())
-        setup = self._setup(test.keywords.setup)
         keywords = Keywords(test.keywords.normal)
-        teardown = self._teardown(test.keywords.teardown)
result.timeout = test.timeout # TODO: Cleaner implementation to ...
         result.status = 'RUNNING'       # ... activate timeouts
         self._context.start_test(result)
         if test.timeout:
test.timeout.replace_variables(self._variables) # FIXME: Should not change model state!!
             test.timeout.start()
-        setup.run(self._context)
+        failures = TestFailures()
+        self._run_test_setup(test.keywords.setup, failures)
         try:
-            keywords.run(self._context)
+            if failures.run_allowed:
+                keywords.run(self._context)
         except ExecutionFailed, err:
-            result.message = unicode(err)
-            result.status = 'FAIL'
-        else:
-            result.status = 'PASS'
-        teardown.run(self._context)
+            failures.test_failure = unicode(err)
+        self._run_test_teardown(test.keywords.teardown, failures)
+        result.message = failures.message
+        result.status = 'FAIL' if failures else 'PASS'
         result.endtime = utils.get_timestamp()
         self._context.end_test(result)

+    def _run_test_setup(self, setup, failures):
+        failure = self._run_setup_or_teardown(setup, 'setup')
+        if failure is not None:
+            failures.setup_failure = failure
+
+    def _run_test_teardown(self, teardown, failures):
+        failure = self._run_setup_or_teardown(teardown, 'teardown')
+        if failure is not None:
+            failures.teardown_failure = failure
+
+    def _run_setup_or_teardown(self, data, type):
+        if not data:
+            return None
+        try:
+            name = self._variables.replace_string(data.name)
+        except DataError, err:
+            return unicode(err)
+        kw = Keyword(name, data.args, type=type)
+        try:
+            kw.run(self._context)
+        except ExecutionFailed, err:
+            return unicode(err)
+
     def _setup(self, setup):
         if not setup:
             return Setup('', ())
=======================================
--- /utest/new_running/test_running.py  Thu May 23 23:32:42 2013
+++ /utest/new_running/test_running.py  Fri May 24 00:45:38 2013
@@ -1,8 +1,13 @@
 import unittest
 from StringIO import StringIO
+from os.path import abspath, dirname, normpath, join

 from robot.utils.asserts import assert_equals
-from robot.new_running import TestSuite
+from robot.new_running import TestSuite, TestSuiteBuilder
+
+
+CURDIR = dirname(abspath(__file__))
+DATADIR = normpath(join(CURDIR, '..', '..', 'atest', 'testdata', 'misc'))


 def run(suite):
@@ -10,6 +15,10 @@
     return result.suite


+def build_and_run(path):
+    return run(TestSuiteBuilder().build(join(DATADIR, path)))
+
+
 def assert_suite(suite, name, status, tests=1):
     assert_equals(suite.name, name)
     assert_equals(suite.status, status)
@@ -89,3 +98,24 @@
         assert_suite(result, 'Suite', 'FAIL', tests=2)
         assert_test(result.tests[0], 'T1', 'FAIL', msg='Error message')
         assert_test(result.tests[1], 'T2', 'FAIL', ('added tag',), 'Error')
+
+
+class TestSetupAndTeardown(unittest.TestCase):
+
+    def setUp(self):
+        self.tests = build_and_run('setups_and_teardowns.txt').tests
+
+    def test_passing_setup_and_teardown(self):
+        assert_test(self.tests[0], 'Test with setup and teardown', 'PASS')
+
+    def test_failing_setup(self):
+        assert_test(self.tests[1], 'Test with failing setup', 'FAIL',
+                    msg='Setup failed:\nTest Setup')
+
+    def test_failing_teardown(self):
+        assert_test(self.tests[2], 'Test with failing teardown', 'FAIL',
+                    msg='Teardown failed:\nTest Teardown')
+
+    def test_failing_test_with_failing_teardown(self):
+ assert_test(self.tests[3], 'Failing test with failing teardown', 'FAIL',
+                    msg='Keyword\n\nAlso teardown failed:\nTest Teardown')

==============================================================================
Revision: 7ad690543152
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 02:51:16 2013
Log:      Fixed handling using non-existing variables in variable table.

Update issue 561
Summary: Not possible to use imported variables in variable table
Labels: Type-Defect
Status: Done
The problem with non-existing variables is now fixed. I'm really surprised there were no tests for this earlier.

Also changed the type of this issue from enhancement to defect. I don't think the earlier behavior was a feature.
http://code.google.com/p/robotframework/source/detail?r=7ad690543152

Modified:
 /atest/robot/variables/variable_table.txt
 /atest/testdata/variables/variable_table.txt
 /src/robot/variables/variables.py

=======================================
--- /atest/robot/variables/variable_table.txt   Wed Apr 17 05:42:46 2013
+++ /atest/robot/variables/variable_table.txt   Fri May 24 02:51:16 2013
@@ -50,12 +50,44 @@
     Check Test Case     ${TEST NAME}

 Using Scalar List Should Fail
-    ${path}=    Normalize Path    ${DATADIR}/variables/variable_table.txt
-    ${msg}=    Catenate
-    ...    Error in file '${path}' in table 'Variables': Setting variable
-    ...    '\${SCALAR LIST}' failed: Creating a scalar variable with a list
-    ...    value in the Variable table is no longer possible. Create a list
-    ...    variable '\@{SCALAR LIST}' and use it as a scalar variable
-    ...    '\${SCALAR LIST}' instead.
     Check Test Case     ${TEST NAME}
-    Check Log Message    ${ERRORS[0]}    ${msg}    ERROR
+    Creating Variable Should Have Failed    ${ERRORS[0]}    \${SCALAR LIST}
+ ... Creating a scalar variable with a list value in the Variable table
+    ...    is no longer possible. Create a list variable '\@{SCALAR LIST}'
+    ...    and use it as a scalar variable '\${SCALAR LIST}' instead.
+
+Creating variable using non-existing variable fails
+    Check Test Case    ${TEST NAME}
+    Creating Variable Should Have Failed    ${ERRORS[4]}    \${NONEX 1}
+    ...    Non-existing variable '\${NON EXISTING}'.
+    Creating Variable Should Have Failed    ${ERRORS[5]}    \${NONEX 2A}
+    ...    Non-existing variable '\${NON EX}'.
+    Creating Variable Should Have Failed    ${ERRORS[6]}    \${NONEX 2B}
+    ...    Non-existing variable '\${NONEX 2A}'.
+
+Using variable created from non-existing variable in imports fails
+    Creating Variable Should Have Failed    ${ERRORS[1]}    \${NONEX 3}
+    ...    Non-existing variable '\${NON EXISTING VARIABLE}'.
+    Import Should Have Failed    ${ERRORS[2]}    Resource
+    ...    Non-existing variable '\${NONEX 3}'.
+    Import Should Have Failed    ${ERRORS[3]}    Library
+    ...    Non-existing variable '\${NONEX 3}'.
+
+*** Keywords ***
+Creating Variable Should Have Failed
+    [Arguments]    ${error}    ${name}    @{message}
+    ${path} =    Normalize Path    ${DATADIR}/variables/variable_table.txt
+    ${msg} =    Catenate
+    ...    Error in file '${path}' in table 'Variables':
+    ...    Setting variable '${name}' failed:
+    ...    @{message}
+    Check Log Message    ${error}    ${msg}    ERROR
+
+Import Should Have Failed
+    [Arguments]    ${error}    ${name}    @{message}
+    ${path} =    Normalize Path    ${DATADIR}/variables/variable_table.txt
+    ${msg} =    Catenate
+    ...    Error in file '${path}' in table 'Settings':
+    ...    Replacing variables from setting '${name}' failed:
+    ...    @{message}
+    Check Log Message    ${error}    ${msg}    ERROR
=======================================
--- /atest/testdata/variables/variable_table.txt        Tue May  7 07:19:17 2013
+++ /atest/testdata/variables/variable_table.txt        Fri May 24 02:51:16 2013
@@ -22,6 +22,14 @@
 ${THREE DOTS}     ...
 @{3DOTS LIST}     ...   ...
${SCALAR LIST} I am a scalar list with many items +${NONEX 1} Creating variable based on ${NON EXISTING} variable fails.
+${NONEX 2A}       This ${NON EX} is used for creating another variable.
+${NONEX 2B}       ${NONEX 2A}
+${NONEX 3}        This ${NON EXISTING VARIABLE} is used in imports.
+
+*** Settings ***
+Resource          ${NONEX 3}
+Library           ${NONEX 3}

 *** Test Case ***
 Scalar String
@@ -107,5 +115,10 @@
     Should Be Equal    ${sos}    ...---...

 Using Scalar List Should Fail
-    [Documentation]    FAIL    Non-existing variable '${SCALAR LIST}'.
-    Log    ${SCALAR LIST}
+    Variable Should Not Exist    ${SCALAR LIST}
+
+Creating variable using non-existing variable fails
+    Variable Should Not Exist    ${NONEX 1}
+    Variable Should Not Exist    ${NONEX 2A}
+    Variable Should Not Exist    ${NONEX 2B}
+    Variable Should Not Exist    ${NONEX 3}
=======================================
--- /src/robot/variables/variables.py   Wed May 22 02:41:21 2013
+++ /src/robot/variables/variables.py   Fri May 24 02:51:16 2013
@@ -90,12 +90,15 @@

     def _solve_delayed(self, name, value):
         if isinstance(value, DelayedVariable):
-            value = value.resolve(name, self)
-            self[name] = value
+            return value.resolve(name, self)
         return value

     def resolve_delayed(self):
-        self.values()
+        for var in self:
+            try:
+ self[var] # getting variable indirectly resolves it if needed
+            except DataError:
+                pass

     def _validate_var_name(self, name):
         if not is_var(name):
@@ -330,21 +333,24 @@
                 self.set(name, value)

     def set_from_variable_table(self, variables, overwrite=False):
+        def report_invalid_syntax(name, error):
+            # TODO: Error reporting is disabled with new model
+            if not hasattr(variables, 'report_invalid_syntax'):
+                return
+ variables.report_invalid_syntax("Setting variable '%s' failed: %s"
+                                            % (name, unicode(error)))
         for var in variables:
             if not var:
                 continue  # TODO: Remove compatibility with old run model.
             try:
                 name, value = self._get_var_table_name_and_value(
-                    var.name, var.value)
+                    var.name, var.value, report_invalid_syntax)
                 if overwrite or not self.contains(name):
                     self.set(name, value)
             except DataError, err:
-                # TODO: Error reporting is disabled with new model
-                if hasattr(variables, 'report_invalid_syntax'):
- variables.report_invalid_syntax("Setting variable '%s' failed: %s" - % (var.name, unicode(err)))
+                report_invalid_syntax(var.name, err)

-    def _get_var_table_name_and_value(self, name, value):
+    def _get_var_table_name_and_value(self, name, value, error_reporter):
         self._validate_var_name(name)
         # TODO: Old run gives as scalars as list, new as strings. Clean up!
         if is_scalar_var(name) and isinstance(value, basestring):
@@ -352,7 +358,7 @@
         else:
             self._validate_var_is_not_scalar_list(name, value)
value = [self._unescape_leading_trailing_spaces(cell) for cell in value]
-        return name, DelayedVariable(value)
+        return name, DelayedVariable(value, error_reporter)

     def _unescape_leading_trailing_spaces(self, item):
         if item.endswith(' \\'):
@@ -419,10 +425,21 @@

 class DelayedVariable(object):

-    def __init__(self, value):
+    def __init__(self, value, error_reporter):
         self._value = value
+        self._error_reporter = error_reporter

     def resolve(self, name, variables):
+        try:
+            value = self._resolve(name, variables)
+        except DataError, err:
+            self._error_reporter(name, err)
+            variables.pop(name)
+            raise DataError("Non-existing variable '%s'." % name)
+        variables[name] = value
+        return value
+
+    def _resolve(self, name, variables):
         if is_list_var(name):
             return variables.replace_list(self._value)
         return variables.replace_scalar(self._value[0])

==============================================================================
Revision: c067ab3b5b43
Branch:   default
Author:   Pekka Klärck
Date:     Fri May 24 02:51:25 2013
Log:      Automated merge with https://code.google.com/p/robotframework/
http://code.google.com/p/robotframework/source/detail?r=c067ab3b5b43


--

--- You received this message because you are subscribed to the Google Groups "robotframework-commit" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to