Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
J
jadx
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
open-source
jadx
Commits
ca21ca5d
Commit
ca21ca5d
authored
Mar 24, 2019
by
Skylot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
test: rewrite Spock tests to JUnit 5
parent
6e66dc25
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
535 additions
and
545 deletions
+535
-545
build.gradle
build.gradle
+0
-1
AType.java
jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
+0
-3
TestAttributeStorage.groovy
...re/src/test/groovy/jadx/tests/TestAttributeStorage.groovy
+0
-82
TestNameMapper.groovy
jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy
+0
-39
TestSignatureParser.groovy
...ore/src/test/groovy/jadx/tests/TestSignatureParser.groovy
+0
-107
TestStringUtils.groovy
jadx-core/src/test/groovy/jadx/tests/TestStringUtils.groovy
+0
-43
TestsTest.java
jadx-core/src/test/java/jadx/tests/TestsTest.java
+0
-49
AttributeStorageTest.java
...test/java/jadx/tests/functional/AttributeStorageTest.java
+84
-0
NameMapperTest.java
...e/src/test/java/jadx/tests/functional/NameMapperTest.java
+44
-0
SignatureParserTest.java
.../test/java/jadx/tests/functional/SignatureParserTest.java
+128
-0
StringUtilsTest.java
.../src/test/java/jadx/tests/functional/StringUtilsTest.java
+52
-0
TestLoopDetection3.java
...java/jadx/tests/integration/loops/TestLoopDetection3.java
+0
-2
TestJumpManager.groovy
...gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy
+0
-117
TestStringRef.groovy
jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy
+0
-71
TestVersionsComparator.groovy
.../test/groovy/jadx/gui/tests/TestVersionsComparator.groovy
+0
-31
VersionComparatorTest.java
.../src/test/java/jadx/gui/update/VersionComparatorTest.java
+33
-0
JumpManagerTest.java
jadx-gui/src/test/java/jadx/gui/utils/JumpManagerTest.java
+123
-0
StringRefTest.java
...ui/src/test/java/jadx/gui/utils/search/StringRefTest.java
+71
-0
No files found.
build.gradle
View file @
ca21ca5d
...
@@ -40,7 +40,6 @@ allprojects {
...
@@ -40,7 +40,6 @@ allprojects {
testCompile
'ch.qos.logback:logback-classic:1.2.3'
testCompile
'ch.qos.logback:logback-classic:1.2.3'
testCompile
'org.hamcrest:hamcrest-library:2.1'
testCompile
'org.hamcrest:hamcrest-library:2.1'
testCompile
'org.mockito:mockito-core:2.25.1'
testCompile
'org.mockito:mockito-core:2.25.1'
testCompile
'org.spockframework:spock-core:1.3-groovy-2.5'
testImplementation
'org.junit.jupiter:junit-jupiter-api:5.4.1'
testImplementation
'org.junit.jupiter:junit-jupiter-api:5.4.1'
testRuntimeOnly
'org.junit.jupiter:junit-jupiter-engine:5.4.1'
testRuntimeOnly
'org.junit.jupiter:junit-jupiter-engine:5.4.1'
...
...
jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
View file @
ca21ca5d
...
@@ -54,7 +54,4 @@ public class AType<T extends IAttribute> {
...
@@ -54,7 +54,4 @@ public class AType<T extends IAttribute> {
public
static
final
AType
<
DeclareVariablesAttr
>
DECLARE_VARIABLES
=
new
AType
<>();
public
static
final
AType
<
DeclareVariablesAttr
>
DECLARE_VARIABLES
=
new
AType
<>();
public
static
final
AType
<
LoopLabelAttr
>
LOOP_LABEL
=
new
AType
<>();
public
static
final
AType
<
LoopLabelAttr
>
LOOP_LABEL
=
new
AType
<>();
public
static
final
AType
<
IgnoreEdgeAttr
>
IGNORE_EDGE
=
new
AType
<>();
public
static
final
AType
<
IgnoreEdgeAttr
>
IGNORE_EDGE
=
new
AType
<>();
private
AType
()
{
}
}
}
jadx-core/src/test/groovy/jadx/tests/TestAttributeStorage.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.tests
import
jadx.core.dex.attributes.AType
import
jadx.core.dex.attributes.AttributeStorage
import
jadx.core.dex.attributes.IAttribute
import
spock.lang.Specification
import
static
jadx
.
core
.
dex
.
attributes
.
AFlag
.
SYNTHETIC
class
TestAttributeStorage
extends
Specification
{
AttributeStorage
storage
def
setup
()
{
storage
=
new
AttributeStorage
()
}
def
"add flag"
()
{
when:
storage
.
add
(
SYNTHETIC
)
then:
storage
.
contains
(
SYNTHETIC
)
}
def
"remove flag"
()
{
setup:
storage
.
add
(
SYNTHETIC
)
when:
storage
.
remove
(
SYNTHETIC
)
then:
!
storage
.
contains
(
SYNTHETIC
)
}
def
TEST
=
new
AType
<
TestAttr
>()
class
TestAttr
implements
IAttribute
{
AType
<
TestAttr
>
getType
()
{
TEST
}
}
def
"add attribute"
()
{
setup:
def
attr
=
new
TestAttr
()
when:
storage
.
add
(
attr
)
then:
storage
.
contains
(
TEST
)
storage
.
get
(
TEST
)
==
attr
}
def
"remove attribute"
()
{
setup:
def
attr
=
new
TestAttr
()
storage
.
add
(
attr
)
when:
storage
.
remove
(
attr
)
then:
!
storage
.
contains
(
TEST
)
storage
.
get
(
TEST
)
==
null
}
def
"remove attribute other"
()
{
setup:
def
attr
=
new
TestAttr
()
storage
.
add
(
attr
)
when:
storage
.
remove
(
new
TestAttr
())
then:
storage
.
contains
(
TEST
)
storage
.
get
(
TEST
)
==
attr
}
def
"clear"
()
{
setup:
storage
.
add
(
SYNTHETIC
)
storage
.
add
(
new
TestAttr
())
when:
storage
.
clear
()
then:
!
storage
.
contains
(
SYNTHETIC
)
!
storage
.
contains
(
TEST
)
}
}
jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.tests
import
spock.lang.Specification
import
static
jadx
.
core
.
deobf
.
NameMapper
.
isValidFullIdentifier
class
TestNameMapper
extends
Specification
{
def
"test is Valid Full Identifier"
()
{
expect:
isValidFullIdentifier
(
valid
)
where:
valid
<<
[
'C'
,
'Cc'
,
'b.C'
,
'b.Cc'
,
'aAa.b.Cc'
,
'a.b.Cc'
,
'a.b.C_c'
,
'a.b.C$c'
,
'a.b.C9'
]
}
def
"test is not Valid Full Identifier"
()
{
expect:
!
isValidFullIdentifier
(
invalid
)
where:
invalid
<<
[
''
,
'5'
,
'7A'
,
'.C'
,
'b.9C'
,
'b..C'
,
]
}
}
jadx-core/src/test/groovy/jadx/tests/TestSignatureParser.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.tests
import
jadx.core.dex.instructions.args.ArgType
import
jadx.core.dex.nodes.parser.SignatureParser
import
spock.lang.Specification
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.*
class
TestSignatureParser
extends
Specification
{
def
"simple types"
()
{
expect:
new
SignatureParser
(
str
).
consumeType
()
==
result
where:
str
|
result
""
|
null
"I"
|
INT
"[I"
|
array
(
INT
)
"Ljava/lang/Object;"
|
OBJECT
"[Ljava/lang/Object;"
|
array
(
OBJECT
)
"[[I"
|
array
(
array
(
INT
))
}
def
"generics"
()
{
expect:
new
SignatureParser
(
str
).
consumeType
()
==
result
where:
str
|
result
"TD;"
|
genericType
(
"D"
)
"La<TV;Lb;>;"
|
generic
(
"La;"
,
genericType
(
"V"
),
object
(
"b"
))
"La<Lb<Lc;>;>;"
|
generic
(
"La;"
,
generic
(
"Lb;"
,
object
(
"Lc;"
)))
"La/b/C<Ld/E<Lf/G;>;>;"
|
generic
(
"La/b/C;"
,
generic
(
"Ld/E;"
,
object
(
"Lf/G;"
)))
"La<TD;>.c;"
|
genericInner
(
generic
(
"La;"
,
genericType
(
"D"
)),
"c"
,
null
)
"La<TD;>.c/d;"
|
genericInner
(
generic
(
"La;"
,
genericType
(
"D"
)),
"c.d"
,
null
)
"La<Lb;>.c<TV;>;"
|
genericInner
(
generic
(
"La;"
,
object
(
"Lb;"
)),
"c"
,
genericType
(
"V"
))
}
def
"inner generic"
()
{
expect:
new
SignatureParser
(
str
).
consumeType
().
getObject
()
==
result
where:
str
|
result
"La<TV;>.LinkedHashIterator<Lb\$c<Ls;TV;>;>;"
|
"a\$LinkedHashIterator"
}
def
"wildcards"
()
{
expect:
new
SignatureParser
(
"La<$s>;"
).
consumeType
()
==
generic
(
"La;"
,
r
as
ArgType
[])
where:
s
|
r
"*"
|
wildcard
()
"+Lb;"
|
wildcard
(
object
(
"b"
),
1
)
"-Lb;"
|
wildcard
(
object
(
"b"
),
-
1
)
"+TV;"
|
wildcard
(
genericType
(
"V"
),
1
)
"-TV;"
|
wildcard
(
genericType
(
"V"
),
-
1
)
"**"
|
[
wildcard
(),
wildcard
()]
"*Lb;"
|
[
wildcard
(),
object
(
"b"
)]
"*TV;"
|
[
wildcard
(),
genericType
(
"V"
)]
"TV;*"
|
[
genericType
(
"V"
),
wildcard
()]
"Lb;*"
|
[
object
(
"b"
),
wildcard
()]
"***"
|
[
wildcard
(),
wildcard
(),
wildcard
()]
"*Lb;*"
|
[
wildcard
(),
object
(
"b"
),
wildcard
()]
}
def
"generic map"
()
{
expect:
new
SignatureParser
(
str
).
consumeGenericMap
()
==
result
.
collectEntries
{
[
genericType
(
it
.
key
),
it
.
value
]
}
where:
str
|
result
""
|
[:]
"<T:Ljava/lang/Object;>"
|
[
"T"
:
[]]
"<K:Ljava/lang/Object;LongType:Ljava/lang/Object;>"
|
[
"K"
:
[],
"LongType"
:
[]]
"<ResultT:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
[
"ResultT"
:
[
object
(
"java.lang.Exception"
)]]
}
def
"method args"
()
{
when:
def
argTypes
=
new
SignatureParser
(
"(Ljava/util/List<*>;)V"
).
consumeMethodArgs
()
then:
argTypes
.
size
()
==
1
argTypes
.
get
(
0
)
==
generic
(
"Ljava/util/List;"
,
wildcard
())
}
def
"method args 2"
()
{
when:
def
argTypes
=
new
SignatureParser
(
"(La/b/C<TT;>.d/E;)V"
).
consumeMethodArgs
()
then:
argTypes
.
size
()
==
1
def
argType
=
argTypes
.
get
(
0
)
argType
.
getObject
().
indexOf
(
'/'
)
==
-
1
argTypes
.
get
(
0
)
==
genericInner
(
generic
(
"La/b/C;"
,
genericType
(
"T"
)),
"d.E"
,
null
)
}
def
"generic map: bad signature"
()
{
when:
def
map
=
new
SignatureParser
(
"<A:Ljava/lang/Object;B"
).
consumeGenericMap
()
then:
notThrown
(
NullPointerException
)
map
.
isEmpty
()
}
}
jadx-core/src/test/groovy/jadx/tests/TestStringUtils.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.tests
import
jadx.api.JadxArgs
import
jadx.core.utils.StringUtils
import
spock.lang.Specification
class
TestStringUtils
extends
Specification
{
def
"unescape string"
()
{
def
args
=
new
JadxArgs
()
args
.
setEscapeUnicode
(
true
)
def
stringUtils
=
new
StringUtils
(
args
)
expect:
stringUtils
.
unescapeString
(
input
)
==
"\"$expected\""
where:
input
|
expected
""
|
""
"'"
|
"'"
"a"
|
"a"
"\n"
|
"\\n"
"\t"
|
"\\t"
"\r"
|
"\\r"
"\b"
|
"\\b"
"\f"
|
"\\f"
"\\"
|
"\\\\"
"\""
|
"\\\""
"\u1234"
|
"\\u1234"
}
def
"unescape char"
()
{
expect:
new
StringUtils
(
new
JadxArgs
()).
unescapeChar
(
input
as
char
)
==
"'$expected'"
where:
input
|
expected
'a'
|
"a"
' '
|
" "
'\n'
|
"\\n"
'\''
|
"\\\'"
'\0'
|
"\\u0000"
}
}
jadx-core/src/test/java/jadx/tests/TestsTest.java
deleted
100644 → 0
View file @
6e66dc25
package
jadx
.
tests
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertTrue
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.
fail
;
import
java.io.File
;
import
java.io.IOException
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
import
java.nio.file.Paths
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
public
class
TestsTest
{
@Test
public
void
noJUnit4Asssertions
()
throws
IOException
{
noJUnit4Asssertions
(
"."
);
noJUnit4Asssertions
(
"../jadx-cli"
);
noJUnit4Asssertions
(
"../jadx-gui"
);
}
private
void
noJUnit4Asssertions
(
String
path
)
throws
IOException
{
Path
dir
=
Paths
.
get
(
path
,
"src/test/java"
);
assertTrue
(
Files
.
exists
(
dir
));
Files
.
walk
(
dir
)
.
filter
(
p
->
p
.
getFileName
().
toString
().
endsWith
(
".java"
)
&&
!
p
.
getFileName
().
toString
().
endsWith
(
TestsTest
.
class
.
getSimpleName
()
+
".java"
))
.
forEach
(
p
->
{
try
{
List
<
String
>
lines
=
Files
.
readAllLines
(
p
);
for
(
String
line
:
lines
)
{
if
(
line
.
contains
(
"org.junit.Assert"
))
{
String
className
=
dir
.
relativize
(
p
).
toString
();
className
=
className
.
substring
(
0
,
className
.
length
()
-
".java"
.
length
());
className
=
className
.
replace
(
File
.
separatorChar
,
'.'
);
fail
(
"Test class "
+
className
+
" should be migrated to JUnit 5"
);
}
}
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
});
}
}
jadx-core/src/test/java/jadx/tests/functional/AttributeStorageTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
tests
.
functional
;
import
org.junit.jupiter.api.BeforeEach
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.dex.attributes.AType
;
import
jadx.core.dex.attributes.AttributeStorage
;
import
jadx.core.dex.attributes.IAttribute
;
import
static
jadx
.
core
.
dex
.
attributes
.
AFlag
.
SYNTHETIC
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
import
static
org
.
hamcrest
.
Matchers
.
nullValue
;
public
class
AttributeStorageTest
{
private
AttributeStorage
storage
;
@BeforeEach
public
void
setup
()
{
storage
=
new
AttributeStorage
();
}
@Test
public
void
testAdd
()
{
storage
.
add
(
SYNTHETIC
);
assertThat
(
storage
.
contains
(
SYNTHETIC
),
is
(
true
));
}
@Test
public
void
testRemove
()
{
storage
.
add
(
SYNTHETIC
);
storage
.
remove
(
SYNTHETIC
);
assertThat
(
storage
.
contains
(
SYNTHETIC
),
is
(
false
));
}
public
static
final
AType
<
TestAttr
>
TEST
=
new
AType
<>();
public
static
class
TestAttr
implements
IAttribute
{
@Override
public
AType
<
TestAttr
>
getType
()
{
return
TEST
;
}
}
@Test
public
void
testAddAttribute
()
{
TestAttr
attr
=
new
TestAttr
();
storage
.
add
(
attr
);
assertThat
(
storage
.
contains
(
TEST
),
is
(
true
));
assertThat
(
storage
.
get
(
TEST
),
is
(
attr
));
}
@Test
public
void
testRemoveAttribute
()
{
TestAttr
attr
=
new
TestAttr
();
storage
.
add
(
attr
);
storage
.
remove
(
attr
);
assertThat
(
storage
.
contains
(
TEST
),
is
(
false
));
assertThat
(
storage
.
get
(
TEST
),
nullValue
());
}
@Test
public
void
testRemoveOtherAttribute
()
{
TestAttr
attr
=
new
TestAttr
();
storage
.
add
(
attr
);
storage
.
remove
(
new
TestAttr
());
assertThat
(
storage
.
contains
(
TEST
),
is
(
true
));
assertThat
(
storage
.
get
(
TEST
),
is
(
attr
));
}
@Test
public
void
clear
()
{
storage
.
add
(
SYNTHETIC
);
storage
.
add
(
new
TestAttr
());
storage
.
clear
();
assertThat
(
storage
.
contains
(
SYNTHETIC
),
is
(
false
));
assertThat
(
storage
.
contains
(
TEST
),
is
(
false
));
assertThat
(
storage
.
get
(
TEST
),
nullValue
());
}
}
jadx-core/src/test/java/jadx/tests/functional/NameMapperTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
tests
.
functional
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.deobf.NameMapper
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
public
class
NameMapperTest
{
@Test
public
void
testValidFullIdentifiers
()
{
String
[]
validNames
=
{
"C"
,
"Cc"
,
"b.C"
,
"b.Cc"
,
"aAa.b.Cc"
,
"a.b.Cc"
,
"a.b.C_c"
,
"a.b.C$c"
,
"a.b.C9"
};
for
(
String
validName
:
validNames
)
{
assertThat
(
NameMapper
.
isValidFullIdentifier
(
validName
),
is
(
true
));
}
}
@Test
public
void
testInvalidFullIdentifiers
()
{
String
[]
invalidNames
=
{
""
,
"5"
,
"7A"
,
".C"
,
"b.9C"
,
"b..C"
,
};
for
(
String
invalidName
:
invalidNames
)
{
assertThat
(
NameMapper
.
isValidFullIdentifier
(
invalidName
),
is
(
false
));
}
}
}
jadx-core/src/test/java/jadx/tests/functional/SignatureParserTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
tests
.
functional
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
org.junit.jupiter.api.Test
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.nodes.parser.SignatureParser
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
INT
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
OBJECT
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
array
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
generic
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
genericInner
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
genericType
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
object
;
import
static
jadx
.
core
.
dex
.
instructions
.
args
.
ArgType
.
wildcard
;
import
static
java
.
util
.
Collections
.
emptyList
;
import
static
java
.
util
.
Collections
.
singletonList
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
anEmptyMap
;
import
static
org
.
hamcrest
.
Matchers
.
hasSize
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
class
SignatureParserTest
{
@Test
public
void
testSimpleTypes
()
{
checkType
(
""
,
null
);
checkType
(
"I"
,
INT
);
checkType
(
"[I"
,
array
(
INT
));
checkType
(
"Ljava/lang/Object;"
,
OBJECT
);
checkType
(
"[Ljava/lang/Object;"
,
array
(
OBJECT
));
checkType
(
"[[I"
,
array
(
array
(
INT
)));
}
private
static
void
checkType
(
String
str
,
ArgType
type
)
{
assertThat
(
new
SignatureParser
(
str
).
consumeType
(),
is
(
type
));
}
@Test
public
void
testGenerics
()
{
checkType
(
"TD;"
,
genericType
(
"D"
));
checkType
(
"La<TV;Lb;>;"
,
generic
(
"La;"
,
new
ArgType
[]{
genericType
(
"V"
),
object
(
"b"
)}));
checkType
(
"La<Lb<Lc;>;>;"
,
generic
(
"La;"
,
new
ArgType
[]{
generic
(
"Lb;"
,
new
ArgType
[]{
object
(
"Lc;"
)})}));
checkType
(
"La/b/C<Ld/E<Lf/G;>;>;"
,
generic
(
"La/b/C;"
,
new
ArgType
[]{
generic
(
"Ld/E;"
,
new
ArgType
[]{
object
(
"Lf/G;"
)})}));
checkType
(
"La<TD;>.c;"
,
genericInner
(
generic
(
"La;"
,
new
ArgType
[]{
genericType
(
"D"
)}),
"c"
,
null
));
checkType
(
"La<TD;>.c/d;"
,
genericInner
(
generic
(
"La;"
,
new
ArgType
[]{
genericType
(
"D"
)}),
"c.d"
,
null
));
checkType
(
"La<Lb;>.c<TV;>;"
,
genericInner
(
generic
(
"La;"
,
new
ArgType
[]{
object
(
"Lb;"
)}),
"c"
,
new
ArgType
[]{
genericType
(
"V"
)}));
}
@Test
public
void
testInnerGeneric
()
{
String
signature
=
"La<TV;>.LinkedHashIterator<Lb$c<Ls;TV;>;>;"
;
String
objectStr
=
new
SignatureParser
(
signature
).
consumeType
().
getObject
();
assertThat
(
objectStr
,
is
(
"a$LinkedHashIterator"
));
}
@Test
public
void
testWildcards
()
{
checkWildcards
(
"*"
,
wildcard
());
checkWildcards
(
"+Lb;"
,
wildcard
(
object
(
"b"
),
1
));
checkWildcards
(
"-Lb;"
,
wildcard
(
object
(
"b"
),
-
1
));
checkWildcards
(
"+TV;"
,
wildcard
(
genericType
(
"V"
),
1
));
checkWildcards
(
"-TV;"
,
wildcard
(
genericType
(
"V"
),
-
1
));
checkWildcards
(
"**"
,
wildcard
(),
wildcard
());
checkWildcards
(
"*Lb;"
,
wildcard
(),
object
(
"b"
));
checkWildcards
(
"*TV;"
,
wildcard
(),
genericType
(
"V"
));
checkWildcards
(
"TV;*"
,
genericType
(
"V"
),
wildcard
());
checkWildcards
(
"Lb;*"
,
object
(
"b"
),
wildcard
());
checkWildcards
(
"***"
,
wildcard
(),
wildcard
(),
wildcard
());
checkWildcards
(
"*Lb;*"
,
wildcard
(),
object
(
"b"
),
wildcard
());
}
private
static
void
checkWildcards
(
String
w
,
ArgType
...
types
)
{
ArgType
parsedType
=
new
SignatureParser
(
"La<"
+
w
+
">;"
).
consumeType
();
ArgType
expectedType
=
generic
(
"La;"
,
types
);
assertThat
(
parsedType
,
is
(
expectedType
));
}
@Test
public
void
testGenericMap
()
{
checkGenerics
(
""
);
checkGenerics
(
"<T:Ljava/lang/Object;>"
,
"T"
,
emptyList
());
checkGenerics
(
"<K:Ljava/lang/Object;LongType:Ljava/lang/Object;>"
,
"K"
,
emptyList
(),
"LongType"
,
emptyList
());
checkGenerics
(
"<ResultT:Ljava/lang/Exception;:Ljava/lang/Object;>"
,
"ResultT"
,
singletonList
(
object
(
"java.lang.Exception"
)));
}
@SuppressWarnings
(
"unchecked"
)
private
static
void
checkGenerics
(
String
g
,
Object
...
objs
)
{
Map
<
ArgType
,
List
<
ArgType
>>
map
=
new
SignatureParser
(
g
).
consumeGenericMap
();
Map
<
ArgType
,
List
<
ArgType
>>
expectedMap
=
new
LinkedHashMap
<>();
for
(
int
i
=
0
;
i
<
objs
.
length
;
i
+=
2
)
{
ArgType
generic
=
genericType
((
String
)
objs
[
i
]);
List
<
ArgType
>
list
=
(
List
<
ArgType
>)
objs
[
i
+
1
];
expectedMap
.
put
(
generic
,
list
);
}
assertThat
(
map
,
is
(
expectedMap
));
}
@Test
public
void
testMethodArgs
()
{
List
<
ArgType
>
argTypes
=
new
SignatureParser
(
"(Ljava/util/List<*>;)V"
).
consumeMethodArgs
();
assertThat
(
argTypes
,
hasSize
(
1
));
assertThat
(
argTypes
.
get
(
0
),
is
(
generic
(
"Ljava/util/List;"
,
new
ArgType
[]{
wildcard
()})));
}
@Test
public
void
testMethodArgs2
()
{
List
<
ArgType
>
argTypes
=
new
SignatureParser
(
"(La/b/C<TT;>.d/E;)V"
).
consumeMethodArgs
();
assertThat
(
argTypes
,
hasSize
(
1
));
ArgType
argType
=
argTypes
.
get
(
0
);
assertThat
(
argType
.
getObject
().
indexOf
(
'/'
),
is
(-
1
));
assertThat
(
argType
,
is
(
genericInner
(
generic
(
"La/b/C;"
,
new
ArgType
[]{
genericType
(
"T"
)}),
"d.E"
,
null
)));
}
@Test
public
void
testBadGenericMap
()
{
Map
<
ArgType
,
List
<
ArgType
>>
map
=
new
SignatureParser
(
"<A:Ljava/lang/Object;B"
).
consumeGenericMap
();
assertThat
(
map
,
anEmptyMap
());
}
}
jadx-core/src/test/java/jadx/tests/functional/StringUtilsTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
tests
.
functional
;
import
org.junit.jupiter.api.Test
;
import
jadx.api.JadxArgs
;
import
jadx.core.utils.StringUtils
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
class
StringUtilsTest
{
private
StringUtils
stringUtils
;
@Test
public
void
testStringUnescape
()
{
JadxArgs
args
=
new
JadxArgs
();
args
.
setEscapeUnicode
(
true
);
stringUtils
=
new
StringUtils
(
args
);
checkStringUnescape
(
""
,
""
);
checkStringUnescape
(
"'"
,
"'"
);
checkStringUnescape
(
"a"
,
"a"
);
checkStringUnescape
(
"\n"
,
"\\n"
);
checkStringUnescape
(
"\t"
,
"\\t"
);
checkStringUnescape
(
"\r"
,
"\\r"
);
checkStringUnescape
(
"\b"
,
"\\b"
);
checkStringUnescape
(
"\f"
,
"\\f"
);
checkStringUnescape
(
"\\"
,
"\\\\"
);
checkStringUnescape
(
"\""
,
"\\\""
);
checkStringUnescape
(
"\u1234"
,
"\\u1234"
);
}
private
void
checkStringUnescape
(
String
input
,
String
result
)
{
assertThat
(
stringUtils
.
unescapeString
(
input
),
is
(
"\""
+
result
+
"\""
));
}
@Test
public
void
testCharUnescape
()
{
stringUtils
=
new
StringUtils
(
new
JadxArgs
());
checkCharUnescape
(
'a'
,
"a"
);
checkCharUnescape
(
' '
,
" "
);
checkCharUnescape
(
'\n'
,
"\\n"
);
checkCharUnescape
(
'\''
,
"\\\'"
);
checkCharUnescape
(
'\0'
,
"\\u0000"
);
}
private
void
checkCharUnescape
(
char
input
,
String
result
)
{
assertThat
(
stringUtils
.
unescapeChar
(
input
),
is
(
"'"
+
result
+
"'"
));
}
}
jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopDetection3.java
View file @
ca21ca5d
package
jadx
.
tests
.
integration
.
loops
;
package
jadx
.
tests
.
integration
.
loops
;
import
org.junit.jupiter.api.Test
;
import
org.junit.jupiter.api.Test
;
import
org.junit.jupiter.api.extension.ExtendWith
;
import
jadx.NotYetImplemented
;
import
jadx.NotYetImplemented
;
import
jadx.NotYetImplementedExtension
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.tests.api.IntegrationTest
;
import
jadx.tests.api.IntegrationTest
;
...
...
jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.gui.tests
import
jadx.gui.utils.JumpManager
import
jadx.gui.utils.JumpPosition
import
spock.lang.Specification
class
TestJumpManager
extends
Specification
{
JumpManager
jm
def
setup
()
{
jm
=
new
JumpManager
()
}
def
"empty history"
()
{
expect:
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
null
}
def
"empty history 2"
()
{
expect:
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
null
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
null
jm
.
getPrev
()
==
null
}
def
"1 element"
()
{
when:
jm
.
addPosition
(
Mock
(
JumpPosition
))
then:
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
null
}
def
"2 elements"
()
{
when:
def
mock1
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock1
)
def
mock2
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock2
)
// 1 - 2@
then:
noExceptionThrown
()
jm
.
getPrev
()
==
mock1
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
mock2
jm
.
getNext
()
==
null
}
def
"navigation"
()
{
expect:
def
mock1
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock1
)
// 1@
def
mock2
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock2
)
// 1 - 2@
jm
.
getPrev
()
==
mock1
// 1@ - 2
def
mock3
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock3
)
// 1 - 3@
jm
.
getNext
()
==
null
jm
.
getPrev
()
==
mock1
// 1@ - 3
jm
.
getNext
()
==
mock3
}
def
"navigation2"
()
{
expect:
def
mock1
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock1
)
// 1@
def
mock2
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock2
)
// 1 - 2@
def
mock3
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock3
)
// 1 - 2 - 3@
def
mock4
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock4
)
// 1 - 2 - 3 - 4@
jm
.
getPrev
()
==
mock3
// 1 - 2 - 3@ - 4
jm
.
getPrev
()
==
mock2
// 1 - 2@ - 3 - 4
def
mock5
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock5
)
// 1 - 2 - 5@
jm
.
getNext
()
==
null
jm
.
getNext
()
==
null
jm
.
getPrev
()
==
mock2
// 1 - 2@ - 5
jm
.
getPrev
()
==
mock1
// 1@ - 2 - 5
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
mock2
// 1 - 2@ - 5
jm
.
getNext
()
==
mock5
// 1 - 2 - 5@
jm
.
getNext
()
==
null
}
def
"add same element"
()
{
when:
def
mock
=
Mock
(
JumpPosition
)
jm
.
addPosition
(
mock
)
jm
.
addPosition
(
mock
)
then:
noExceptionThrown
()
jm
.
getPrev
()
==
null
jm
.
getNext
()
==
null
}
}
jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.gui.tests
import
jadx.gui.utils.search.StringRef
import
spock.lang.Specification
import
static
jadx
.
gui
.
utils
.
search
.
StringRef
.
fromStr
import
static
jadx
.
gui
.
utils
.
search
.
StringRef
.
subString
class
TestStringRef
extends
Specification
{
def
"test substring"
()
{
expect:
s1
.
toString
()
==
expected
s1
==
fromStr
(
expected
)
where:
s1
|
expected
fromStr
(
"a"
)
|
"a"
subString
(
"a"
,
0
)
|
"a"
subString
(
"a"
,
1
)
|
""
subString
(
"a"
,
0
,
0
)
|
""
subString
(
"a"
,
0
,
1
)
|
"a"
subString
(
"abc"
,
1
,
2
)
|
"b"
subString
(
"abc"
,
2
)
|
"c"
subString
(
"abc"
,
2
,
3
)
|
"c"
}
def
"compare with original substring"
()
{
expect:
s
==
expected
where:
s
|
expected
"a"
.
substring
(
0
)
|
"a"
"a"
.
substring
(
1
)
|
""
"a"
.
substring
(
0
,
0
)
|
""
"a"
.
substring
(
0
,
1
)
|
"a"
}
def
"test trim"
()
{
expect:
s
.
trim
().
toString
()
==
expected
where:
s
|
expected
fromStr
(
"a"
)
|
"a"
fromStr
(
" a "
)
|
"a"
fromStr
(
"\ta"
)
|
"a"
subString
(
"a b c"
,
1
)
|
"b c"
subString
(
"a b\tc"
,
1
,
4
)
|
"b"
subString
(
"a b\tc"
,
2
,
3
)
|
"b"
}
def
"test split"
()
{
expect:
StringRef
.
split
(
s
,
d
)
==
(
expected
as
String
[]).
collect
{
fromStr
(
it
)
}
if
(!
Arrays
.
equals
(
s
.
split
(
d
),
(
expected
).
toArray
(
new
String
[
0
])))
{
throw
new
IllegalArgumentException
(
"Don't match with original split: "
+
" s='"
+
s
+
"' d='"
+
d
+
"', expected:"
+
expected
+
", got: "
+
Arrays
.
toString
(
s
.
split
(
d
)));
}
where:
s
|
d
|
expected
"abc"
|
"b"
|
[
"a"
,
"c"
]
"abc"
|
"a"
|
[
""
,
"bc"
]
"abc"
|
"c"
|
[
"ab"
]
"abc"
|
"d"
|
[
"abc"
]
"abbbc"
|
"b"
|
[
"a"
,
""
,
""
,
"c"
]
"abbbc"
|
"bb"
|
[
"a"
,
"bc"
]
"abbbc"
|
"bbb"
|
[
"a"
,
"c"
]
"abbbc"
|
"bbc"
|
[
"ab"
]
"abbbc"
|
"bbbc"
|
[
"a"
]
}
}
jadx-gui/src/test/groovy/jadx/gui/tests/TestVersionsComparator.groovy
deleted
100644 → 0
View file @
6e66dc25
package
jadx.gui.tests
import
jadx.gui.update.VersionComparator
import
spock.lang.Specification
class
TestVersionsComparator
extends
Specification
{
def
"test"
()
{
expect:
VersionComparator
.
compare
(
s1
,
s2
)
==
expected
VersionComparator
.
compare
(
s2
,
s1
)
==
-
expected
where:
s1
|
s2
|
expected
""
|
""
|
0
"1"
|
"1"
|
0
"1"
|
"2"
|
-
1
"1.1"
|
"1.1"
|
0
"0.5"
|
"0.5"
|
0
"0.5"
|
"0.5.0"
|
0
"0.5"
|
"0.5.00"
|
0
"0.5"
|
"0.5.0.0"
|
0
"0.5"
|
"0.5.0.1"
|
-
1
"0.5.0"
|
"0.5.0"
|
0
"0.5.0"
|
"0.5.1"
|
-
1
"0.5"
|
"0.5.1"
|
-
1
"0.4.8"
|
"0.5"
|
-
1
"0.4.8"
|
"0.5.0"
|
-
1
"0.4.8"
|
"0.6"
|
-
1
}
}
jadx-gui/src/test/java/jadx/gui/update/VersionComparatorTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
gui
.
update
;
import
org.junit.jupiter.api.Test
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
class
VersionComparatorTest
{
@Test
public
void
testCompare
()
{
checkCompare
(
""
,
""
,
0
);
checkCompare
(
"1"
,
"1"
,
0
);
checkCompare
(
"1"
,
"2"
,
-
1
);
checkCompare
(
"1.1"
,
"1.1"
,
0
);
checkCompare
(
"0.5"
,
"0.5"
,
0
);
checkCompare
(
"0.5"
,
"0.5.0"
,
0
);
checkCompare
(
"0.5"
,
"0.5.00"
,
0
);
checkCompare
(
"0.5"
,
"0.5.0.0"
,
0
);
checkCompare
(
"0.5"
,
"0.5.0.1"
,
-
1
);
checkCompare
(
"0.5.0"
,
"0.5.0"
,
0
);
checkCompare
(
"0.5.0"
,
"0.5.1"
,
-
1
);
checkCompare
(
"0.5"
,
"0.5.1"
,
-
1
);
checkCompare
(
"0.4.8"
,
"0.5"
,
-
1
);
checkCompare
(
"0.4.8"
,
"0.5.0"
,
-
1
);
checkCompare
(
"0.4.8"
,
"0.6"
,
-
1
);
}
private
static
void
checkCompare
(
String
a
,
String
b
,
int
result
)
{
assertThat
(
VersionComparator
.
compare
(
a
,
b
),
is
(
result
));
assertThat
(
VersionComparator
.
compare
(
b
,
a
),
is
(-
result
));
}
}
jadx-gui/src/test/java/jadx/gui/utils/JumpManagerTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
gui
.
utils
;
import
org.junit.jupiter.api.BeforeEach
;
import
org.junit.jupiter.api.Test
;
import
jadx.gui.treemodel.TextNode
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
nullValue
;
import
static
org
.
hamcrest
.
Matchers
.
sameInstance
;
class
JumpManagerTest
{
private
JumpManager
jm
;
@BeforeEach
public
void
setup
()
{
jm
=
new
JumpManager
();
}
@Test
public
void
testEmptyHistory
()
{
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
}
@Test
public
void
testEmptyHistory2
()
{
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
assertThat
(
jm
.
getPrev
(),
nullValue
());
}
@Test
public
void
testOneElement
()
{
jm
.
addPosition
(
makeJumpPos
());
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
}
@Test
public
void
testTwoElements
()
{
JumpPosition
pos1
=
makeJumpPos
();
jm
.
addPosition
(
pos1
);
JumpPosition
pos2
=
makeJumpPos
();
jm
.
addPosition
(
pos2
);
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos1
));
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
sameInstance
(
pos2
));
assertThat
(
jm
.
getNext
(),
nullValue
());
}
@Test
public
void
testNavigation
()
{
JumpPosition
pos1
=
makeJumpPos
();
jm
.
addPosition
(
pos1
);
// 1@
JumpPosition
pos2
=
makeJumpPos
();
jm
.
addPosition
(
pos2
);
// 1 - 2@
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos1
));
// 1@ - 2
JumpPosition
pos3
=
makeJumpPos
();
jm
.
addPosition
(
pos3
);
// 1 - 3@
assertThat
(
jm
.
getNext
(),
nullValue
());
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos1
));
// 1@ - 3
assertThat
(
jm
.
getNext
(),
sameInstance
(
pos3
));
}
@Test
public
void
testNavigation2
()
{
JumpPosition
pos1
=
makeJumpPos
();
jm
.
addPosition
(
pos1
);
// 1@
JumpPosition
pos2
=
makeJumpPos
();
jm
.
addPosition
(
pos2
);
// 1 - 2@
JumpPosition
pos3
=
makeJumpPos
();
jm
.
addPosition
(
pos3
);
// 1 - 2 - 3@
JumpPosition
pos4
=
makeJumpPos
();
jm
.
addPosition
(
pos4
);
// 1 - 2 - 3 - 4@
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos3
));
// 1 - 2 - 3@ - 4
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos2
));
// 1 - 2@ - 3 - 4
JumpPosition
pos5
=
makeJumpPos
();
jm
.
addPosition
(
pos5
);
// 1 - 2 - 5@
assertThat
(
jm
.
getNext
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos2
));
// 1 - 2@ - 5
assertThat
(
jm
.
getPrev
(),
sameInstance
(
pos1
));
// 1@ - 2 - 5
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
sameInstance
(
pos2
));
// 1 - 2@ - 5
assertThat
(
jm
.
getNext
(),
sameInstance
(
pos5
));
// 1 - 2 - 5@
assertThat
(
jm
.
getNext
(),
nullValue
());
}
@Test
public
void
addSame
()
{
JumpPosition
pos
=
makeJumpPos
();
jm
.
addPosition
(
pos
);
jm
.
addPosition
(
pos
);
assertThat
(
jm
.
getPrev
(),
nullValue
());
assertThat
(
jm
.
getNext
(),
nullValue
());
}
private
JumpPosition
makeJumpPos
()
{
return
new
JumpPosition
(
new
TextNode
(
""
),
0
);
}
}
jadx-gui/src/test/java/jadx/gui/utils/search/StringRefTest.java
0 → 100644
View file @
ca21ca5d
package
jadx
.
gui
.
utils
.
search
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.stream.Collectors
;
import
org.junit.jupiter.api.Test
;
import
static
jadx
.
gui
.
utils
.
search
.
StringRef
.
fromStr
;
import
static
jadx
.
gui
.
utils
.
search
.
StringRef
.
subString
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
is
;
class
StringRefTest
{
@Test
public
void
testConvert
()
{
assertThat
(
fromStr
(
"a"
).
toString
(),
is
(
"a"
));
}
@Test
public
void
testSubstring
()
{
checkStr
(
subString
(
"a"
,
0
),
"a"
);
checkStr
(
subString
(
"a"
,
1
),
""
);
checkStr
(
subString
(
"a"
,
0
,
0
),
""
);
checkStr
(
subString
(
"a"
,
0
,
1
),
"a"
);
checkStr
(
subString
(
"abc"
,
1
,
2
),
"b"
);
checkStr
(
subString
(
"abc"
,
2
),
"c"
);
checkStr
(
subString
(
"abc"
,
2
,
3
),
"c"
);
}
public
static
void
checkStr
(
StringRef
ref
,
String
str
)
{
assertThat
(
ref
.
toString
(),
is
(
str
));
assertThat
(
ref
,
is
(
fromStr
(
str
)));
}
@Test
public
void
testTrim
()
{
checkTrim
(
fromStr
(
"a"
),
"a"
);
checkTrim
(
fromStr
(
" a "
),
"a"
);
checkTrim
(
fromStr
(
"\ta"
),
"a"
);
checkTrim
(
subString
(
"a b c"
,
1
),
"b c"
);
checkTrim
(
subString
(
"a b\tc"
,
1
,
4
),
"b"
);
checkTrim
(
subString
(
"a b\tc"
,
2
,
3
),
"b"
);
}
private
static
void
checkTrim
(
StringRef
ref
,
String
result
)
{
assertThat
(
ref
.
trim
().
toString
(),
is
(
result
));
}
@Test
public
void
testSplit
()
{
checkSplit
(
"abc"
,
"b"
,
"a"
,
"c"
);
checkSplit
(
"abc"
,
"a"
,
""
,
"bc"
);
checkSplit
(
"abc"
,
"c"
,
"ab"
);
checkSplit
(
"abc"
,
"d"
,
"abc"
);
checkSplit
(
"abbbc"
,
"b"
,
"a"
,
""
,
""
,
"c"
);
checkSplit
(
"abbbc"
,
"bb"
,
"a"
,
"bc"
);
checkSplit
(
"abbbc"
,
"bbb"
,
"a"
,
"c"
);
checkSplit
(
"abbbc"
,
"bbc"
,
"ab"
);
checkSplit
(
"abbbc"
,
"bbbc"
,
"a"
);
}
private
static
void
checkSplit
(
String
str
,
String
splitBy
,
String
...
result
)
{
List
<
StringRef
>
expectedStringRegList
=
Arrays
.
stream
(
result
).
map
(
StringRef:
:
fromStr
).
collect
(
Collectors
.
toList
());
assertThat
(
StringRef
.
split
(
str
,
splitBy
),
is
(
expectedStringRegList
));
// compare with original split
assertThat
(
str
.
split
(
splitBy
),
is
(
result
));
}
}
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