Commit 8fbddf15 authored by Florent Xicluna's avatar Florent Xicluna

Merged revisions 79030-79032 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r79030 | florent.xicluna | 2010-03-17 20:05:04 +0100 (mer, 17 mar 2010) | 2 lines

  Cleanup in test_import and test_coding.
........
  r79031 | florent.xicluna | 2010-03-17 20:15:56 +0100 (mer, 17 mar 2010) | 2 lines

  Cleanup some test cases using check_warnings and check_py3k_warnings.
........
  r79032 | florent.xicluna | 2010-03-17 21:05:11 +0100 (mer, 17 mar 2010) | 2 lines

  Fix and check cgi module deprecation warnings.  Revert an unwanted rename in test_import.
........
parent a85c3bd1
from test.support import run_unittest from test.support import run_unittest, check_warnings
import cgi import cgi
import os import os
import sys import sys
import tempfile import tempfile
import unittest import unittest
from io import StringIO from io import StringIO
from warnings import catch_warnings, filterwarnings
class HackedSysModule: class HackedSysModule:
# The regression test will have real values in sys.argv, which # The regression test will have real values in sys.argv, which
...@@ -15,10 +14,6 @@ class HackedSysModule: ...@@ -15,10 +14,6 @@ class HackedSysModule:
cgi.sys = HackedSysModule() cgi.sys = HackedSysModule()
try:
from io import StringIO
except ImportError:
from io import StringIO
class ComparableException: class ComparableException:
def __init__(self, err): def __init__(self, err):
...@@ -117,7 +112,7 @@ def gen_result(data, environ): ...@@ -117,7 +112,7 @@ def gen_result(data, environ):
result = {} result = {}
for k, v in dict(form).items(): for k, v in dict(form).items():
result[k] = type(v) is list and form.getlist(k) or v.value result[k] = isinstance(v, list) and form.getlist(k) or v.value
return result return result
...@@ -133,7 +128,7 @@ class CgiTests(unittest.TestCase): ...@@ -133,7 +128,7 @@ class CgiTests(unittest.TestCase):
env = {'QUERY_STRING': orig} env = {'QUERY_STRING': orig}
fs = cgi.FieldStorage(environ=env) fs = cgi.FieldStorage(environ=env)
if type(expect) == type({}): if isinstance(expect, dict):
# test dict interface # test dict interface
self.assertEqual(len(expect), len(fs)) self.assertEqual(len(expect), len(fs))
self.assertEqual(norm(expect.keys()), norm(fs.keys())) self.assertEqual(norm(expect.keys()), norm(fs.keys()))
...@@ -308,20 +303,16 @@ this is the content of the fake file ...@@ -308,20 +303,16 @@ this is the content of the fake file
self.assertEqual(result, v) self.assertEqual(result, v)
def test_deprecated_parse_qs(self): def test_deprecated_parse_qs(self):
# this func is moved to urlparse, this is just a sanity check # this func is moved to urllib.parse, this is just a sanity check
with catch_warnings(): with check_warnings(('cgi.parse_qs is deprecated, use urllib.parse.'
filterwarnings('ignore', 'parse_qs instead', DeprecationWarning)):
'cgi.parse_qs is deprecated, use urllib.parse.parse_qs instead',
DeprecationWarning)
self.assertEqual({'a': ['A1'], 'B': ['B3'], 'b': ['B2']}, self.assertEqual({'a': ['A1'], 'B': ['B3'], 'b': ['B2']},
cgi.parse_qs('a=A1&b=B2&B=B3')) cgi.parse_qs('a=A1&b=B2&B=B3'))
def test_deprecated_parse_qsl(self): def test_deprecated_parse_qsl(self):
# this func is moved to urlparse, this is just a sanity check # this func is moved to urllib.parse, this is just a sanity check
with catch_warnings(): with check_warnings(('cgi.parse_qsl is deprecated, use urllib.parse.'
filterwarnings('ignore', 'parse_qsl instead', DeprecationWarning)):
'cgi.parse_qsl is deprecated, use urllib.parse.parse_qsl instead',
DeprecationWarning)
self.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')], self.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')],
cgi.parse_qsl('a=A1&b=B2&B=B3')) cgi.parse_qsl('a=A1&b=B2&B=B3'))
......
import test.support, unittest import test.support, unittest
from test.support import TESTFN, unlink from test.support import TESTFN, unlink, unload
import os, sys import os, sys
class CodingTest(unittest.TestCase): class CodingTest(unittest.TestCase):
...@@ -17,9 +17,8 @@ class CodingTest(unittest.TestCase): ...@@ -17,9 +17,8 @@ class CodingTest(unittest.TestCase):
path = os.path.dirname(__file__) path = os.path.dirname(__file__)
filename = os.path.join(path, module_name + '.py') filename = os.path.join(path, module_name + '.py')
fp = open(filename, "rb") with open(filename, "rb") as fp:
bytes = fp.read() bytes = fp.read()
fp.close()
self.assertRaises(SyntaxError, compile, bytes, filename, 'exec') self.assertRaises(SyntaxError, compile, bytes, filename, 'exec')
def test_exec_valid_coding(self): def test_exec_valid_coding(self):
...@@ -30,9 +29,8 @@ class CodingTest(unittest.TestCase): ...@@ -30,9 +29,8 @@ class CodingTest(unittest.TestCase):
def test_file_parse(self): def test_file_parse(self):
# issue1134: all encodings outside latin-1 and utf-8 fail on # issue1134: all encodings outside latin-1 and utf-8 fail on
# multiline strings and long lines (>512 columns) # multiline strings and long lines (>512 columns)
if TESTFN in sys.modules: unload(TESTFN)
del sys.modules[TESTFN] sys.path.insert(0, os.curdir)
sys.path.insert(0, ".")
filename = TESTFN + ".py" filename = TESTFN + ".py"
f = open(filename, "w") f = open(filename, "w")
try: try:
...@@ -45,21 +43,20 @@ class CodingTest(unittest.TestCase): ...@@ -45,21 +43,20 @@ class CodingTest(unittest.TestCase):
__import__(TESTFN) __import__(TESTFN)
finally: finally:
f.close() f.close()
unlink(TESTFN+".py") unlink(filename)
unlink(TESTFN+".pyc") unlink(filename + "c")
sys.path.pop(0) unload(TESTFN)
del sys.path[0]
def test_error_from_string(self): def test_error_from_string(self):
# See http://bugs.python.org/issue6289 # See http://bugs.python.org/issue6289
input = "# coding: ascii\n\N{SNOWMAN}".encode('utf-8') input = "# coding: ascii\n\N{SNOWMAN}".encode('utf-8')
try: with self.assertRaises(SyntaxError) as c:
compile(input, "<string>", "exec") compile(input, "<string>", "exec")
except SyntaxError as e: expected = "'ascii' codec can't decode byte 0xe2 in position 16: " \
expected = "'ascii' codec can't decode byte 0xe2 in position 16: " \ "ordinal not in range(128)"
"ordinal not in range(128)" self.assertTrue(c.exception.args[0].startswith(expected))
self.assertTrue(str(e).startswith(expected))
else:
self.fail("didn't raise")
def test_main(): def test_main():
test.support.run_unittest(CodingTest) test.support.run_unittest(CodingTest)
......
...@@ -8,9 +8,8 @@ import shutil ...@@ -8,9 +8,8 @@ import shutil
import stat import stat
import sys import sys
import unittest import unittest
import warnings from test.support import (unlink, TESTFN, unload, run_unittest, is_jython,
from test.support import (unlink, TESTFN, unload, run_unittest, check_warnings, EnvironmentVarGuard, swap_attr, swap_item)
TestFailed, EnvironmentVarGuard, swap_attr, swap_item)
def remove_files(name): def remove_files(name):
...@@ -19,12 +18,15 @@ def remove_files(name): ...@@ -19,12 +18,15 @@ def remove_files(name):
name + ".pyo", name + ".pyo",
name + ".pyw", name + ".pyw",
name + "$py.class"): name + "$py.class"):
if os.path.exists(f): unlink(f)
os.remove(f)
class ImportTests(unittest.TestCase): class ImportTests(unittest.TestCase):
def tearDown(self):
unload(TESTFN)
setUp = tearDown
def test_case_sensitivity(self): def test_case_sensitivity(self):
# Brief digression to test that import is case-sensitive: if we got # Brief digression to test that import is case-sensitive: if we got
# this far, we know for sure that "random" exists. # this far, we know for sure that "random" exists.
...@@ -45,7 +47,7 @@ class ImportTests(unittest.TestCase): ...@@ -45,7 +47,7 @@ class ImportTests(unittest.TestCase):
# The extension is normally ".py", perhaps ".pyw". # The extension is normally ".py", perhaps ".pyw".
source = TESTFN + ext source = TESTFN + ext
pyo = TESTFN + ".pyo" pyo = TESTFN + ".pyo"
if sys.platform.startswith('java'): if is_jython:
pyc = TESTFN + "$py.class" pyc = TESTFN + "$py.class"
else: else:
pyc = TESTFN + ".pyc" pyc = TESTFN + ".pyc"
...@@ -66,15 +68,15 @@ class ImportTests(unittest.TestCase): ...@@ -66,15 +68,15 @@ class ImportTests(unittest.TestCase):
except ImportError as err: except ImportError as err:
self.fail("import from %s failed: %s" % (ext, err)) self.fail("import from %s failed: %s" % (ext, err))
self.assertEquals(mod.a, a, self.assertEqual(mod.a, a,
"module loaded (%s) but contents invalid" % mod) "module loaded (%s) but contents invalid" % mod)
self.assertEquals(mod.b, b, self.assertEqual(mod.b, b,
"module loaded (%s) but contents invalid" % mod) "module loaded (%s) but contents invalid" % mod)
finally: finally:
unlink(source) unlink(source)
unlink(pyc) unlink(pyc)
unlink(pyo) unlink(pyo)
del sys.modules[TESTFN] unload(TESTFN)
sys.path.insert(0, os.curdir) sys.path.insert(0, os.curdir)
try: try:
...@@ -100,21 +102,22 @@ class ImportTests(unittest.TestCase): ...@@ -100,21 +102,22 @@ class ImportTests(unittest.TestCase):
fn = fname + 'c' fn = fname + 'c'
if not os.path.exists(fn): if not os.path.exists(fn):
fn = fname + 'o' fn = fname + 'o'
if not os.path.exists(fn): raise TestFailed("__import__ did " if not os.path.exists(fn):
"not result in creation of either a .pyc or .pyo file") self.fail("__import__ did not result in creation of "
"either a .pyc or .pyo file")
s = os.stat(fn) s = os.stat(fn)
self.assertEquals(stat.S_IMODE(s.st_mode), self.assertEqual(stat.S_IMODE(s.st_mode),
stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
finally: finally:
os.umask(oldmask) os.umask(oldmask)
remove_files(TESTFN) remove_files(TESTFN)
if TESTFN in sys.modules: del sys.modules[TESTFN] unload(TESTFN)
del sys.path[0] del sys.path[0]
def test_imp_module(self): def test_imp_module(self):
# Verify that the imp module can correctly load and find .py files # Verify that the imp module can correctly load and find .py files
import imp, os import imp, os
# XXX (ncoghlan): It would be nice to use test_support.CleanImport # XXX (ncoghlan): It would be nice to use support.CleanImport
# here, but that breaks because the os module registers some # here, but that breaks because the os module registers some
# handlers in copy_reg on import. Since CleanImport doesn't # handlers in copy_reg on import. Since CleanImport doesn't
# revert that registration, the module is left in a broken # revert that registration, the module is left in a broken
...@@ -144,7 +147,7 @@ class ImportTests(unittest.TestCase): ...@@ -144,7 +147,7 @@ class ImportTests(unittest.TestCase):
# Compile & remove .py file, we only need .pyc (or .pyo). # Compile & remove .py file, we only need .pyc (or .pyo).
with open(filename, 'r') as f: with open(filename, 'r') as f:
py_compile.compile(filename) py_compile.compile(filename)
os.unlink(filename) unlink(filename)
# Need to be able to load from current dir. # Need to be able to load from current dir.
sys.path.append('') sys.path.append('')
...@@ -154,10 +157,8 @@ class ImportTests(unittest.TestCase): ...@@ -154,10 +157,8 @@ class ImportTests(unittest.TestCase):
# Cleanup. # Cleanup.
del sys.path[-1] del sys.path[-1]
for ext in '.pyc', '.pyo': unlink(filename + 'c')
fname = module + ext unlink(filename + 'o')
if os.path.exists(fname):
os.unlink(fname)
def test_failing_import_sticks(self): def test_failing_import_sticks(self):
source = TESTFN + ".py" source = TESTFN + ".py"
...@@ -171,15 +172,11 @@ class ImportTests(unittest.TestCase): ...@@ -171,15 +172,11 @@ class ImportTests(unittest.TestCase):
del sys.modules[TESTFN] del sys.modules[TESTFN]
try: try:
for i in [1, 2, 3]: for i in [1, 2, 3]:
try: self.assertRaises(ZeroDivisionError, __import__, TESTFN)
mod = __import__(TESTFN) self.assertNotIn(TESTFN, sys.modules,
except ZeroDivisionError: "damaged module in sys.modules on %i try" % i)
if TESTFN in sys.modules:
self.fail("damaged module in sys.modules on %i. try" % i)
else:
self.fail("was able to import a damaged module on %i. try" % i)
finally: finally:
sys.path.pop(0) del sys.path[0]
remove_files(TESTFN) remove_files(TESTFN)
def test_import_name_binding(self): def test_import_name_binding(self):
...@@ -210,8 +207,8 @@ class ImportTests(unittest.TestCase): ...@@ -210,8 +207,8 @@ class ImportTests(unittest.TestCase):
try: try:
mod = __import__(TESTFN) mod = __import__(TESTFN)
self.assertIn(TESTFN, sys.modules) self.assertIn(TESTFN, sys.modules)
self.assertEquals(mod.a, 1, "module has wrong attribute values") self.assertEqual(mod.a, 1, "module has wrong attribute values")
self.assertEquals(mod.b, 2, "module has wrong attribute values") self.assertEqual(mod.b, 2, "module has wrong attribute values")
# On WinXP, just replacing the .py file wasn't enough to # On WinXP, just replacing the .py file wasn't enough to
# convince reload() to reparse it. Maybe the timestamp didn't # convince reload() to reparse it. Maybe the timestamp didn't
...@@ -226,18 +223,17 @@ class ImportTests(unittest.TestCase): ...@@ -226,18 +223,17 @@ class ImportTests(unittest.TestCase):
self.assertRaises(ZeroDivisionError, imp.reload, mod) self.assertRaises(ZeroDivisionError, imp.reload, mod)
# But we still expect the module to be in sys.modules. # But we still expect the module to be in sys.modules.
mod = sys.modules.get(TESTFN) mod = sys.modules.get(TESTFN)
self.assertFalse(mod is None, "expected module to be in sys.modules") self.assertIsNot(mod, None, "expected module to be in sys.modules")
# We should have replaced a w/ 10, but the old b value should # We should have replaced a w/ 10, but the old b value should
# stick. # stick.
self.assertEquals(mod.a, 10, "module has wrong attribute values") self.assertEqual(mod.a, 10, "module has wrong attribute values")
self.assertEquals(mod.b, 2, "module has wrong attribute values") self.assertEqual(mod.b, 2, "module has wrong attribute values")
finally: finally:
sys.path.pop(0) del sys.path[0]
remove_files(TESTFN) remove_files(TESTFN)
if TESTFN in sys.modules: unload(TESTFN)
del sys.modules[TESTFN]
def test_file_to_source(self): def test_file_to_source(self):
# check if __file__ points to the source file where available # check if __file__ points to the source file where available
...@@ -255,19 +251,34 @@ class ImportTests(unittest.TestCase): ...@@ -255,19 +251,34 @@ class ImportTests(unittest.TestCase):
ext = mod.__file__[-4:] ext = mod.__file__[-4:]
self.assertIn(ext, ('.pyc', '.pyo')) self.assertIn(ext, ('.pyc', '.pyo'))
finally: finally:
sys.path.pop(0) del sys.path[0]
remove_files(TESTFN) remove_files(TESTFN)
if TESTFN in sys.modules: if TESTFN in sys.modules:
del sys.modules[TESTFN] del sys.modules[TESTFN]
def test_import_name_binding(self):
# import x.y.z binds x in the current namespace.
import test as x
import test.support
self.assertIs(x, test, x.__name__)
self.assertTrue(hasattr(test.support, "__file__"))
# import x.y.z as w binds z as w.
import test.support as y
self.assertIs(y, test.support, y.__name__)
def test_import_initless_directory_warning(self):
with check_warnings(('', ImportWarning)):
# Just a random non-package directory we always expect to be
# somewhere in sys.path...
self.assertRaises(ImportError, __import__, "site-packages")
def test_import_by_filename(self): def test_import_by_filename(self):
path = os.path.abspath(TESTFN) path = os.path.abspath(TESTFN)
try: with self.assertRaises(ImportError) as c:
__import__(path) __import__(path)
except ImportError as err: self.assertEqual("Import by filename is not supported.",
pass c.exception.args[0])
else:
self.fail("import by path didn't raise an exception")
class PycRewritingTests(unittest.TestCase): class PycRewritingTests(unittest.TestCase):
...@@ -302,10 +313,9 @@ func_filename = func.__code__.co_filename ...@@ -302,10 +313,9 @@ func_filename = func.__code__.co_filename
if self.orig_module is not None: if self.orig_module is not None:
sys.modules[self.module_name] = self.orig_module sys.modules[self.module_name] = self.orig_module
else: else:
del sys.modules[self.module_name] unload(self.module_name)
for file_name in self.file_name, self.compiled_name: unlink(self.file_name)
if os.path.exists(file_name): unlink(self.compiled_name)
os.remove(file_name)
if os.path.exists(self.dir_name): if os.path.exists(self.dir_name):
shutil.rmtree(self.dir_name) shutil.rmtree(self.dir_name)
...@@ -406,11 +416,10 @@ class PathsTests(unittest.TestCase): ...@@ -406,11 +416,10 @@ class PathsTests(unittest.TestCase):
class RelativeImportTests(unittest.TestCase): class RelativeImportTests(unittest.TestCase):
def tearDown(self): def tearDown(self):
try: unload("test.relimport")
del sys.modules["test.relimport"] setUp = tearDown
except:
pass
def test_relimport_star(self): def test_relimport_star(self):
# This will import * from .test_import. # This will import * from .test_import.
......
...@@ -1046,14 +1046,9 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests): ...@@ -1046,14 +1046,9 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
self.assertRaises(IOError, bufio.write, b"abcdef") self.assertRaises(IOError, bufio.write, b"abcdef")
def test_max_buffer_size_deprecation(self): def test_max_buffer_size_deprecation(self):
with support.check_warnings() as w: with support.check_warnings(("max_buffer_size is deprecated",
warnings.simplefilter("always", DeprecationWarning) DeprecationWarning)):
self.tp(self.MockRawIO(), 8, 12) self.tp(self.MockRawIO(), 8, 12)
self.assertEqual(len(w.warnings), 1)
warning = w.warnings[0]
self.assertTrue(warning.category is DeprecationWarning)
self.assertEqual(str(warning.message),
"max_buffer_size is deprecated")
class CBufferedWriterTest(BufferedWriterTest): class CBufferedWriterTest(BufferedWriterTest):
...@@ -1109,14 +1104,9 @@ class BufferedRWPairTest(unittest.TestCase): ...@@ -1109,14 +1104,9 @@ class BufferedRWPairTest(unittest.TestCase):
self.assertRaises(self.UnsupportedOperation, pair.detach) self.assertRaises(self.UnsupportedOperation, pair.detach)
def test_constructor_max_buffer_size_deprecation(self): def test_constructor_max_buffer_size_deprecation(self):
with support.check_warnings() as w: with support.check_warnings(("max_buffer_size is deprecated",
warnings.simplefilter("always", DeprecationWarning) DeprecationWarning)):
self.tp(self.MockRawIO(), self.MockRawIO(), 8, 12) self.tp(self.MockRawIO(), self.MockRawIO(), 8, 12)
self.assertEqual(len(w.warnings), 1)
warning = w.warnings[0]
self.assertTrue(warning.category is DeprecationWarning)
self.assertEqual(str(warning.message),
"max_buffer_size is deprecated")
def test_constructor_with_not_readable(self): def test_constructor_with_not_readable(self):
class NotReadable(MockRawIO): class NotReadable(MockRawIO):
......
...@@ -464,30 +464,26 @@ class TestShutil(unittest.TestCase): ...@@ -464,30 +464,26 @@ class TestShutil(unittest.TestCase):
old_dir = os.getcwd() old_dir = os.getcwd()
os.chdir(tmpdir) os.chdir(tmpdir)
try: try:
with captured_stdout() as s: with captured_stdout() as s, check_warnings(quiet=False) as w:
with check_warnings() as w: _make_tarball(base_name, 'dist', compress='compress')
warnings.simplefilter("always")
_make_tarball(base_name, 'dist', compress='compress')
finally: finally:
os.chdir(old_dir) os.chdir(old_dir)
tarball = base_name + '.tar.Z' tarball = base_name + '.tar.Z'
self.assertTrue(os.path.exists(tarball)) self.assertTrue(os.path.exists(tarball))
self.assertEquals(len(w.warnings), 1) self.assertEqual(len(w.warnings), 1)
# same test with dry_run # same test with dry_run
os.remove(tarball) os.remove(tarball)
old_dir = os.getcwd() old_dir = os.getcwd()
os.chdir(tmpdir) os.chdir(tmpdir)
try: try:
with captured_stdout() as s: with captured_stdout() as s, check_warnings(quiet=False) as w:
with check_warnings() as w: _make_tarball(base_name, 'dist', compress='compress',
warnings.simplefilter("always") dry_run=True)
_make_tarball(base_name, 'dist', compress='compress',
dry_run=True)
finally: finally:
os.chdir(old_dir) os.chdir(old_dir)
self.assertTrue(not os.path.exists(tarball)) self.assertFalse(os.path.exists(tarball))
self.assertEquals(len(w.warnings), 1) self.assertEqual(len(w.warnings), 1)
@unittest.skipUnless(zlib, "Requires zlib") @unittest.skipUnless(zlib, "Requires zlib")
@unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
......
...@@ -103,39 +103,30 @@ class ReadWriteTests(unittest.TestCase): ...@@ -103,39 +103,30 @@ class ReadWriteTests(unittest.TestCase):
class TestWarnings(unittest.TestCase): class TestWarnings(unittest.TestCase):
def has_warned(self, w):
self.assertEqual(w.category, RuntimeWarning)
def test_byte_max(self): def test_byte_max(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_BYTE = CHAR_MAX+1 ts.T_BYTE = CHAR_MAX+1
self.has_warned(w)
def test_byte_min(self): def test_byte_min(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_BYTE = CHAR_MIN-1 ts.T_BYTE = CHAR_MIN-1
self.has_warned(w)
def test_ubyte_max(self): def test_ubyte_max(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_UBYTE = UCHAR_MAX+1 ts.T_UBYTE = UCHAR_MAX+1
self.has_warned(w)
def test_short_max(self): def test_short_max(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_SHORT = SHRT_MAX+1 ts.T_SHORT = SHRT_MAX+1
self.has_warned(w)
def test_short_min(self): def test_short_min(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_SHORT = SHRT_MIN-1 ts.T_SHORT = SHRT_MIN-1
self.has_warned(w)
def test_ushort_max(self): def test_ushort_max(self):
with support.check_warnings() as w: with support.check_warnings(('', RuntimeWarning)):
ts.T_USHORT = USHRT_MAX+1 ts.T_USHORT = USHRT_MAX+1
self.has_warned(w)
def test_main(verbose=None): def test_main(verbose=None):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment