Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
package_control_channel
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gaurav Kukreja
package_control_channel
Commits
29314ff3
Commit
29314ff3
authored
Aug 13, 2013
by
Will Bond
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1669 from wbond/better_tests
Better tests
parents
c20ea112
58336fe6
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
342 additions
and
123 deletions
+342
-123
.travis.yml
.travis.yml
+2
-1
channel.json
channel.json
+2
-2
d.json
repository/d.json
+1
-1
g.json
repository/g.json
+10
-10
p.json
repository/p.json
+10
-10
s.json
repository/s.json
+15
-15
test.py
tests/test.py
+302
-84
No files found.
.travis.yml
View file @
29314ff3
language
:
python
python
:
-
"
3.3"
#command to run tests
script
:
nosetests
script
:
python -m unittest
channel.json
View file @
29314ff3
...
...
@@ -4,7 +4,6 @@
"./repository.json"
,
"http://release.latexing.com/packages.json"
,
"http://release.sublimegit.net/packages.json"
,
"https://sublime.wbond.net/packages_2.json"
,
"http://wuub.net/packages.json"
,
"https://bitbucket.org/artyom_smirnov/sublimetext2-packages/raw/master/packages.json"
,
"https://bitbucket.org/jjones028/p4sublime/raw/tip/packages.json"
,
...
...
@@ -151,6 +150,7 @@
"https://raw.github.com/yangsu/sublime-io/master/packages.json"
,
"https://raw.github.com/yangsu/sublime-octopress/master/packages.json"
,
"https://raw.github.com/yangsu/sublime-vhdl/master/packages.json"
,
"https://raw.github.com/zfkun/sublime-kissy-snippets/master/packages.json"
"https://raw.github.com/zfkun/sublime-kissy-snippets/master/packages.json"
,
"https://sublime.wbond.net/packages_2.json"
]
}
repository/d.json
View file @
29314ff3
repository/g.json
View file @
29314ff3
...
...
@@ -298,31 +298,31 @@
]
},
{
"name"
:
"Google
Translate
"
,
"details"
:
"https://github.com/
lfont/Sublime-Text-2-GoogleTranslate-Plugin
"
,
"name"
:
"Google
Spell Check
"
,
"details"
:
"https://github.com/
noahcoad/google-spell-check
"
,
"releases"
:
[
{
"sublime_text"
:
"<3000"
,
"details"
:
"https://github.com/
lfont/Sublime-Text-2-GoogleTranslate-Plugin
/tree/master"
"details"
:
"https://github.com/
noahcoad/google-spell-check
/tree/master"
}
]
},
{
"details"
:
"https://bitbucket.org/nwjlyons/google-search"
,
"name"
:
"Google Translate"
,
"details"
:
"https://github.com/lfont/Sublime-Text-2-GoogleTranslate-Plugin"
,
"releases"
:
[
{
"sublime_text"
:
"
*
"
,
"details"
:
"https://
bitbucket.org/nwjlyons/google-search/src/default
"
"sublime_text"
:
"
<3000
"
,
"details"
:
"https://
github.com/lfont/Sublime-Text-2-GoogleTranslate-Plugin/tree/master
"
}
]
},
{
"name"
:
"Google Spell Check"
,
"details"
:
"https://github.com/noahcoad/google-spell-check"
,
"details"
:
"https://bitbucket.org/nwjlyons/google-search"
,
"releases"
:
[
{
"sublime_text"
:
"
<3000
"
,
"details"
:
"https://
github.com/noahcoad/google-spell-check/tree/master
"
"sublime_text"
:
"
*
"
,
"details"
:
"https://
bitbucket.org/nwjlyons/google-search/src/default
"
}
]
},
...
...
repository/p.json
View file @
29314ff3
...
...
@@ -166,6 +166,16 @@
}
]
},
{
"name"
:
"Paste Laravel"
,
"details"
:
"https://github.com/RobinMalfait/Laravel-paste"
,
"releases"
:
[
{
"sublime_text"
:
"*"
,
"details"
:
"https://github.com/RobinMalfait/Laravel-paste/tree/master"
}
]
},
{
"name"
:
"Paste PDF Text Block"
,
"details"
:
"https://github.com/compleatang/sublimetext-pastepdf"
,
...
...
@@ -205,16 +215,6 @@
}
]
},
{
"name"
:
"Paste Laravel"
,
"details"
:
"https://github.com/RobinMalfait/Laravel-paste"
,
"releases"
:
[
{
"sublime_text"
:
"*"
,
"details"
:
"https://github.com/RobinMalfait/Laravel-paste/tree/master"
}
]
},
{
"name"
:
"PasteSelOnClick"
,
"details"
:
"https://bitbucket.org/Clams/pasteselonclick"
,
...
...
repository/s.json
View file @
29314ff3
...
...
@@ -297,22 +297,22 @@
]
},
{
"name"
:
"Select
ionTools
"
,
"details"
:
"https://github.com/
simonrad/sublime-selection-tools
"
,
"name"
:
"Select
Quoted
"
,
"details"
:
"https://github.com/
int3h/SublimeSelectQuoted
"
,
"releases"
:
[
{
"sublime_text"
:
"
<3000
"
,
"details"
:
"https://github.com/
simonrad/sublime-selection-tools/tree/master
"
"sublime_text"
:
"
*
"
,
"details"
:
"https://github.com/
int3h/SublimeSelectQuoted/tags
"
}
]
},
{
"name"
:
"Select
Quoted
"
,
"details"
:
"https://github.com/
int3h/SublimeSelectQuoted
"
,
"name"
:
"Select
ionTools
"
,
"details"
:
"https://github.com/
simonrad/sublime-selection-tools
"
,
"releases"
:
[
{
"sublime_text"
:
"
*
"
,
"details"
:
"https://github.com/
int3h/SublimeSelectQuoted/tags
"
"sublime_text"
:
"
<3000
"
,
"details"
:
"https://github.com/
simonrad/sublime-selection-tools/tree/master
"
}
]
},
...
...
@@ -792,22 +792,22 @@
]
},
{
"name"
:
"Sort
By
"
,
"details"
:
"https://github.com/
Doi9t/SortB
y"
,
"name"
:
"Sort
Lines (Numerically)
"
,
"details"
:
"https://github.com/
alimony/sublime-sort-numericall
y"
,
"releases"
:
[
{
"sublime_text"
:
"<3000"
,
"details"
:
"https://github.com/
Doi9t/SortB
y/tree/master"
"details"
:
"https://github.com/
alimony/sublime-sort-numericall
y/tree/master"
}
]
},
{
"name"
:
"Sort
Lines (Numerically)
"
,
"details"
:
"https://github.com/
alimony/sublime-sort-numericall
y"
,
"name"
:
"Sort
By
"
,
"details"
:
"https://github.com/
Doi9t/SortB
y"
,
"releases"
:
[
{
"sublime_text"
:
"<3000"
,
"details"
:
"https://github.com/
alimony/sublime-sort-numericall
y/tree/master"
"details"
:
"https://github.com/
Doi9t/SortB
y/tree/master"
}
]
},
...
...
@@ -1534,7 +1534,7 @@
]
},
{
"details"
:
"https://github.com/ostinelli/SublimErl
/tree/package
"
,
"details"
:
"https://github.com/ostinelli/SublimErl"
,
"releases"
:
[
{
"sublime_text"
:
"<3000"
,
...
...
tests/test.py
View file @
29314ff3
#!/usr/bin/env python3
"""Tests for the validity of the channel
file
"""Tests for the validity of the channel
and repository files.
You can run this file directly or with `notetests` (or `python -m unittest`)
from the root directory.
You can run this script directly or with `python -m unittest` from this or the
root directory. For some reason `nosetests` does not pick up the generated tests
even though they are generated at load time.
However, only running the script directly will generate tests for all
repositories in channel.json. This is to reduce the load time for every test run
by travis (and reduces unnecessary failures).
"""
import
os
import
re
import
json
import
unittest
from
collections
import
OrderedDict
from
nose.tools
import
assert_equal
,
assert_in
,
assert_not_in
,
assert_regexp_matches
from
functools
import
wraps
from
urllib.request
import
urlopen
from
urllib.error
import
HTTPError
# Generator tests can't be part of a class, so for consistency
# they are all functions
def
test_channel
():
with
open
(
"channel.json"
)
as
fp
:
data
=
json
.
load
(
fp
)
keys
=
sorted
(
data
.
keys
())
assert_equal
(
keys
,
[
'repositories'
,
'schema_version'
])
################################################################################
# Utilities
assert_equal
(
data
[
'schema_version'
],
'2.0'
)
assert_equal
(
type
(
data
[
'repositories'
]),
list
)
for
repository
in
data
[
'repositories'
]:
assert_equal
(
type
(
repository
),
str
)
def
_open
(
filepath
,
*
args
,
**
kwargs
):
"""Wrapper function that can search one dir above if the desired file
does not exist.
"""
if
not
os
.
path
.
exists
(
filepath
):
filepath
=
os
.
path
.
join
(
".."
,
filepath
)
return
open
(
filepath
,
*
args
,
**
kwargs
)
def
test_repository
():
with
open
(
'repository.json'
)
as
f
:
data
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
keys
=
sorted
(
data
.
keys
())
assert_equal
(
keys
,
[
'includes'
,
'packages'
,
'schema_version'
])
def
generator_class
(
cls
):
"""Class decorator for classes that use test generating methods.
assert_equal
(
data
[
'schema_version'
],
'2.0'
)
assert_equal
(
data
[
'packages'
],
[])
assert_equal
(
type
(
data
[
'includes'
]),
list
)
A class that is decorated with this function will be searched for methods
starting with "generate_" (similar to "test_") and then run like a nosetest
generator.
Note: The generator function must be a classmethod!
for
include
in
data
[
'includes'
]:
assert_equal
(
type
(
include
),
str
)
Generate tests using the following statement:
yield method, (arg1, arg2, arg3) # ...
"""
for
name
in
list
(
cls
.
__dict__
.
keys
()):
generator
=
getattr
(
cls
,
name
)
if
not
name
.
startswith
(
"generate_"
)
or
not
callable
(
generator
):
continue
if
not
generator
.
__class__
.
__name__
==
'method'
:
raise
TypeError
(
"Generator methods must be classmethods"
)
def
test_repository_includes
():
with
open
(
'repository.json'
)
as
f
:
data
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
# Create new methods for each `yield`
for
sub_call
in
generator
()
:
method
,
params
=
sub_call
for
include
in
data
[
'includes'
]:
yield
check_include
,
include
@
wraps
(
method
)
def
wrapper
(
self
,
method
=
method
,
params
=
params
):
return
method
(
self
,
*
params
)
with
open
(
include
)
as
f
:
include_data
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
for
package
in
include_data
[
'packages'
]:
yield
check_package
,
package
if
'releases'
in
package
:
for
release
in
package
[
'releases'
]:
yield
check_release
,
package
,
release
# Do not attempt to print lists/dicts with printed lenght of 1000 or
# more, they are not interesting for us (probably the whole file)
args
=
[]
for
v
in
params
:
string
=
repr
(
v
)
if
len
(
string
)
>
1000
:
args
.
append
(
'...'
)
else
:
args
.
append
(
repr
(
v
))
mname
=
method
.
__name__
if
mname
.
startswith
(
"_test"
):
mname
=
mname
[
1
:]
elif
not
mname
.
startswith
(
"test_"
):
mname
=
"test_"
+
mname
def
check_include
(
filename
):
with
open
(
filename
)
as
f
:
data
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
keys
=
sorted
(
data
.
keys
())
assert_equal
(
keys
,
[
'packages'
,
'schema_version'
])
assert_equal
(
data
[
'schema_version'
],
'2.0'
)
assert_equal
(
type
(
data
[
'packages'
]),
list
)
name
=
"
%
s(
%
s)"
%
(
mname
,
", "
.
join
(
args
))
setattr
(
cls
,
name
,
wrapper
)
# Remove the generator afterwards, it did its work
delattr
(
cls
,
name
)
def
check_package
(
data
):
for
key
in
data
.
keys
():
assert_in
(
key
,
[
'name'
,
'details'
,
'releases'
,
'homepage'
,
'author'
,
'readme'
,
'issues'
,
'donate'
,
'buy'
,
'previous_names'
,
'labels'
])
assert_equal
(
type
(
data
[
key
]),
map_key_type
(
key
))
if
key
in
[
'details'
,
'homepage'
,
'readme'
,
'issues'
,
'donate'
,
'buy'
]:
assert_regexp_matches
(
data
[
key
],
'^https?://'
)
return
cls
if
'details'
not
in
data
:
assert_in
(
'name'
,
data
,
'The key "name" is required if no "details" URL provided'
)
assert_in
(
'homepage'
,
data
,
'The key "homepage" is required if no "details" URL provided'
)
assert_in
(
'author'
,
data
,
'The key "author" is required if no "details" URL provided'
)
assert_in
(
'releases'
,
data
,
'The key "releases" is required if no "details" URL provided'
)
def
get_package_name
(
data
):
"""Gets "name" from a package with a workaround when it's not defined.
def
check_release
(
package
,
data
):
for
key
in
data
.
keys
():
assert_not_in
(
key
,
[
'version'
,
'date'
,
'url'
],
'The version, date and '
+
\
'url keys should not be used in the main repository since a pull '
+
\
'request would be necessary for every release'
)
assert_in
(
key
,
[
'details'
,
'sublime_text'
,
'platforms'
])
Use the last part of details url for the package's name otherwise since
packages must define one of these two keys anyway.
"""
return
data
.
get
(
'name'
)
or
data
.
get
(
'details'
)
.
rsplit
(
'/'
,
1
)[
-
1
]
if
key
in
[
'details'
,
'url'
]:
assert_regexp_matches
(
data
[
key
],
'^https?://'
)
if
key
==
'sublime_text'
:
assert_regexp_matches
(
data
[
key
],
'^(
\
*|<=?
\
d{4}|>=?
\
d{4})$'
)
################################################################################
# Tests
if
key
==
'platforms'
:
assert_in
(
type
(
data
[
key
]),
[
str
,
list
])
if
type
(
data
[
key
])
==
str
:
assert_in
(
data
[
key
],
[
'*'
,
'osx'
,
'linux'
,
'windows'
])
else
:
for
platform
in
data
[
key
]:
assert_in
(
platform
,
[
'*'
,
'osx'
,
'linux'
,
'windows'
])
assert_in
(
'details'
,
data
,
'A release must have a "details" key if it is in '
+
\
'the main repository. For custom releases, a custom repository.json '
+
\
'file must be hosted elsewhere.'
)
class
TestContainer
(
object
):
"""Contains tests that the generators can easily access (when subclassing).
Does not contain tests itself, must be used as mixin with unittest.TestCase.
"""
def
map_key_type
(
key
):
return
{
package_key_types_map
=
{
'name'
:
str
,
'details'
:
str
,
'description'
:
str
,
'releases'
:
list
,
'homepage'
:
str
,
'author'
:
str
,
...
...
@@ -123,12 +120,233 @@ def map_key_type(key):
'buy'
:
str
,
'previous_names'
:
list
,
'labels'
:
list
}
.
get
(
key
)
}
def
_test_repository_keys
(
self
,
include
,
data
):
keys
=
sorted
(
data
.
keys
())
self
.
assertEqual
(
keys
,
[
'packages'
,
'schema_version'
])
self
.
assertEqual
(
data
[
'schema_version'
],
'2.0'
)
self
.
assertIsInstance
(
data
[
'packages'
],
list
)
def
_test_repository_package_order
(
self
,
include
,
data
):
m
=
re
.
search
(
r"(?:^|/)(0-9|[a-z])\.json$"
,
include
)
if
not
m
:
self
.
fail
(
"Include filename does not match"
)
# letter = include[-6]
letter
=
m
.
group
(
1
)
packages
=
[
get_package_name
(
pdata
)
for
pdata
in
data
[
'packages'
]]
# Check if in the correct file
for
package_name
in
packages
:
if
letter
==
'0-9'
:
self
.
assertTrue
(
package_name
[
0
]
.
isdigit
())
else
:
self
.
assertEqual
(
package_name
[
0
]
.
lower
(),
letter
,
"Package inserted in wrong file"
)
# Check package order
self
.
assertEqual
(
packages
,
sorted
(
packages
,
key
=
str
.
lower
))
if
__name__
==
'__main__'
:
# Manually go up one directory if this file is run explicitly
if
not
os
.
path
.
exists
(
repo_file
):
repo_file
=
os
.
path
.
join
(
".."
,
repo_file
)
def
_test_package
(
self
,
include
,
data
):
for
key
in
data
.
keys
():
self
.
assertIn
(
key
,
self
.
package_key_types_map
)
self
.
assertIsInstance
(
data
[
key
],
self
.
package_key_types_map
[
key
])
if
key
in
(
'details'
,
'homepage'
,
'readme'
,
'issues'
,
'donate'
,
'buy'
):
self
.
assertRegex
(
data
[
key
],
'^https?://'
)
if
'details'
not
in
data
:
for
key
in
(
'name'
,
'homepage'
,
'author'
,
'releases'
):
self
.
assertIn
(
key
,
data
,
'
%
r is required if no "details" URL '
'provided'
%
key
)
def
_test_release
(
self
,
package_name
,
data
,
main_repo
=
True
):
# Fail early
if
main_repo
:
self
.
assertIn
(
'details'
,
data
,
'A release must have a "details" key if it is in the '
'main repository. For custom releases, a custom '
'repository.json file must be hosted elsewhere.'
)
elif
not
'details'
in
data
:
for
req
in
(
'url'
,
'version'
,
'date'
):
self
.
assertIn
(
req
,
data
,
'A release must provide "url", "version" and '
'"date" keys if it does not specify "details"'
)
for
k
,
v
in
data
.
items
():
self
.
assertIn
(
k
,
(
'details'
,
'sublime_text'
,
'platforms'
,
'version'
,
'date'
,
'url'
))
if
main_repo
:
self
.
assertNotIn
(
k
,
(
'version'
,
'date'
,
'url'
),
'The version, date and url keys should not be '
'used in the main repository since a pull '
'request would be necessary for every release'
)
else
:
if
k
==
'date'
:
self
.
assertRegex
(
v
,
r"^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$"
)
if
k
==
'details'
:
self
.
assertRegex
(
v
,
'^https?://'
)
if
k
==
'sublime_text'
:
self
.
assertRegex
(
v
,
'^(
\
*|<=?
\
d{4}|>=?
\
d{4})$'
)
if
k
==
'platforms'
:
self
.
assertIsInstance
(
v
,
(
str
,
list
))
if
isinstance
(
v
,
str
):
v
=
[
v
]
for
plat
in
v
:
self
.
assertRegex
(
plat
,
r"^\*|(osx|linux|windows)(-x(32|64))?$"
)
def
_test_error
(
self
,
msg
,
e
=
None
):
if
e
:
if
isinstance
(
e
,
HTTPError
):
self
.
fail
(
"
%
s:
%
s"
%
(
msg
,
e
))
else
:
self
.
fail
(
"
%
s:
%
r"
%
(
msg
,
e
))
else
:
self
.
fail
(
msg
)
@
classmethod
def
_fail
(
cls
,
*
args
):
return
cls
.
_test_error
,
args
@
generator_class
class
ChannelTests
(
TestContainer
,
unittest
.
TestCase
):
maxDiff
=
None
with
_open
(
'channel.json'
)
as
f
:
j
=
json
.
load
(
f
)
def
test_channel_keys
(
self
):
keys
=
sorted
(
self
.
j
.
keys
())
self
.
assertEqual
(
keys
,
[
'repositories'
,
'schema_version'
])
self
.
assertEqual
(
self
.
j
[
'schema_version'
],
'2.0'
)
self
.
assertIsInstance
(
self
.
j
[
'repositories'
],
list
)
for
repo
in
self
.
j
[
'repositories'
]:
self
.
assertIsInstance
(
repo
,
str
)
def
test_channel_repo_order
(
self
):
repos
=
self
.
j
[
'repositories'
]
self
.
assertEqual
(
repos
,
sorted
(
repos
,
key
=
str
.
lower
))
@
classmethod
def
generate_repository_tests
(
cls
):
if
__name__
!=
'__main__'
:
# Do not generate tests for all repositories (those hosted online)
# when testing with unittest's crawler; only when run directly.
return
for
repository
in
cls
.
j
[
'repositories'
]:
if
repository
.
startswith
(
'.'
):
continue
if
not
repository
.
startswith
(
"http"
):
raise
print
(
"fetching
%
s"
%
repository
)
# Download the repository
try
:
with
urlopen
(
repository
)
as
f
:
source
=
f
.
read
()
.
decode
(
"utf-8"
)
except
Exception
as
e
:
yield
cls
.
_fail
(
"Downloading
%
s failed"
%
repository
,
e
)
continue
if
not
source
:
yield
cls
.
_fail
(
"
%
s is empty"
%
repository
)
continue
# Parse the repository (do not consider their includes)
try
:
data
=
json
.
loads
(
source
)
except
Exception
as
e
:
yield
cls
.
_fail
(
"Could not parse
%
s"
%
repository
,
e
)
continue
# Check for the schema version first (and generator failures it's
# badly formatted)
if
'schema_version'
not
in
data
:
yield
cls
.
_fail
(
"No schema_version found in
%
s"
%
repository
)
continue
schema
=
float
(
data
[
'schema_version'
])
if
schema
not
in
(
1.0
,
1.1
,
1.2
,
2.0
):
yield
cls
.
_fail
(
"Unrecognized schema version
%
s in
%
s"
%
(
schema
,
repository
))
continue
# Do not generate 1000 failing tests for not yet updated repos
if
schema
!=
2.0
:
print
(
"schema version
%
s, skipping"
%
data
[
'schema_version'
])
continue
# `repository` is for output during tests only
yield
cls
.
_test_repository_keys
,
(
repository
,
data
)
for
package
in
data
[
'packages'
]:
yield
cls
.
_test_package
,
(
repository
,
package
)
package_name
=
get_package_name
(
package
)
if
'releases'
in
package
:
for
release
in
package
[
'releases'
]:
(
yield
cls
.
_test_release
,
(
"
%
s (
%
s)"
%
(
package_name
,
repository
),
release
,
False
))
@
generator_class
class
RepositoryTests
(
TestContainer
,
unittest
.
TestCase
):
maxDiff
=
None
with
_open
(
'repository.json'
)
as
f
:
j
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
def
test_repository_keys
(
self
):
keys
=
sorted
(
self
.
j
.
keys
())
self
.
assertEqual
(
keys
,
[
'includes'
,
'packages'
,
'schema_version'
])
self
.
assertEqual
(
self
.
j
[
'schema_version'
],
'2.0'
)
self
.
assertEqual
(
self
.
j
[
'packages'
],
[])
self
.
assertIsInstance
(
self
.
j
[
'includes'
],
list
)
for
include
in
self
.
j
[
'includes'
]:
self
.
assertIsInstance
(
include
,
str
)
@
classmethod
def
generate_include_tests
(
cls
):
for
include
in
cls
.
j
[
'includes'
]:
try
:
with
_open
(
include
)
as
f
:
data
=
json
.
load
(
f
,
object_pairs_hook
=
OrderedDict
)
except
Exception
as
e
:
yield
cls
.
_test_error
,
(
"Error while reading
%
r"
%
include
,
e
)
continue
# `include` is for output during tests only
yield
cls
.
_test_repository_keys
,
(
include
,
data
)
yield
cls
.
_test_repository_package_order
,
(
include
,
data
)
for
package
in
data
[
'packages'
]:
yield
cls
.
_test_package
,
(
include
,
package
)
package_name
=
get_package_name
(
package
)
if
'releases'
in
package
:
for
release
in
package
[
'releases'
]:
yield
cls
.
_test_release
,
(
package_name
,
release
)
################################################################################
# Main
if
__name__
==
'__main__'
:
unittest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment