mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
SQLTester: add print command and improve argument error reporting infrastructure.
FossilOrigin-Name: 1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44
This commit is contained in:
@ -25,14 +25,25 @@ public class Tester1 {
|
|||||||
private static final OutputPointer.sqlite3_stmt outStmt
|
private static final OutputPointer.sqlite3_stmt outStmt
|
||||||
= new OutputPointer.sqlite3_stmt();
|
= new OutputPointer.sqlite3_stmt();
|
||||||
|
|
||||||
public static <T> void out(T val){
|
public static void out(Object val){
|
||||||
System.out.print(val);
|
System.out.print(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> void outln(T val){
|
public static void outln(Object val){
|
||||||
System.out.println(val);
|
System.out.println(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void out(Object... vals){
|
||||||
|
int n = 0;
|
||||||
|
for(Object v : vals) out((n++>0 ? " " : "")+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void outln(Object... vals){
|
||||||
|
out(vals); out("\n");
|
||||||
|
}
|
||||||
|
|
||||||
static int affirmCount = 0;
|
static int affirmCount = 0;
|
||||||
public static void affirm(Boolean v){
|
public static void affirm(Boolean v){
|
||||||
++affirmCount;
|
++affirmCount;
|
||||||
@ -42,8 +53,8 @@ public class Tester1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void test1(){
|
private static void test1(){
|
||||||
outln("libversion_number: "
|
outln("libversion_number:",
|
||||||
+ sqlite3_libversion_number()
|
sqlite3_libversion_number()
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ sqlite3_libversion()
|
+ sqlite3_libversion()
|
||||||
+ "\n"
|
+ "\n"
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package org.sqlite.jni.tester;
|
package org.sqlite.jni.tester;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import org.sqlite.jni.*;
|
||||||
import static org.sqlite.jni.SQLite3Jni.*;
|
import static org.sqlite.jni.SQLite3Jni.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,8 +31,10 @@ public class SQLTester {
|
|||||||
private final Outer outer = new Outer();
|
private final Outer outer = new Outer();
|
||||||
private final StringBuilder inputBuffer = new StringBuilder();
|
private final StringBuilder inputBuffer = new StringBuilder();
|
||||||
private String nullView;
|
private String nullView;
|
||||||
private int totalTestCount = 0;
|
private int nTotalTest = 0;
|
||||||
private int testCount;
|
private int nTestFile = 0;
|
||||||
|
private int nTest;
|
||||||
|
private sqlite3[] aDb = {};
|
||||||
|
|
||||||
public SQLTester(){
|
public SQLTester(){
|
||||||
reset();
|
reset();
|
||||||
@ -51,6 +54,11 @@ public class SQLTester {
|
|||||||
outer.outln(vals);
|
outer.outln(vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void out(Object... vals){
|
||||||
|
outer.out(vals);
|
||||||
|
}
|
||||||
|
|
||||||
//! Adds the given test script to the to-test list.
|
//! Adds the given test script to the to-test list.
|
||||||
public void addTestScript(String filename){
|
public void addTestScript(String filename){
|
||||||
listInFiles.add(filename);
|
listInFiles.add(filename);
|
||||||
@ -61,19 +69,29 @@ public class SQLTester {
|
|||||||
// process each input file
|
// process each input file
|
||||||
for(String f : listInFiles){
|
for(String f : listInFiles){
|
||||||
reset();
|
reset();
|
||||||
|
++nTestFile;
|
||||||
final TestScript ts = new TestScript(f);
|
final TestScript ts = new TestScript(f);
|
||||||
ts.setVerbose(this.outer.getVerbose());
|
ts.setVerbose(this.outer.getVerbose());
|
||||||
verbose("Test",ts.getName(),"...");
|
verbose(">>> Test",ts.getName(),"...");
|
||||||
ts.run(this);
|
ts.run(this);
|
||||||
verbose("Ran",testCount,"test(s).");
|
verbose("<<< Ran",nTest,"test(s) in",f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetInputBuffer(){
|
private void resetDbs(){
|
||||||
inputBuffer.delete(0, this.inputBuffer.length());
|
for(sqlite3 db : aDb) sqlite3_close_v2(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getInputBuffer(){
|
StringBuilder resetInputBuffer(){
|
||||||
|
inputBuffer.delete(0, inputBuffer.length());
|
||||||
|
return inputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder getInputBuffer(){
|
||||||
|
return inputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getInputBufferText(){
|
||||||
return inputBuffer.toString();
|
return inputBuffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,17 +102,15 @@ public class SQLTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reset(){
|
void reset(){
|
||||||
testCount = 0;
|
nTest = 0;
|
||||||
nullView = "nil";
|
nullView = "nil";
|
||||||
resetInputBuffer();
|
resetInputBuffer();
|
||||||
|
resetDbs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNullValue(String v){nullView = v;}
|
void setNullValue(String v){nullView = v;}
|
||||||
|
|
||||||
void incrementTestCounter(){
|
void incrementTestCounter(){ ++nTest; ++nTotalTest; }
|
||||||
++testCount;
|
|
||||||
++totalTestCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] argv) throws Exception{
|
public static void main(String[] argv) throws Exception{
|
||||||
final SQLTester t = new SQLTester();
|
final SQLTester t = new SQLTester();
|
||||||
@ -112,21 +128,49 @@ public class SQLTester {
|
|||||||
t.addTestScript(a);
|
t.addTestScript(a);
|
||||||
}
|
}
|
||||||
t.runTests();
|
t.runTests();
|
||||||
|
t.outer.outln("Processed",t.nTotalTest,"test(s) in",t.nTestFile,"file(s).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Base class for test script commands.
|
||||||
|
|
||||||
|
Each subclass must have a ctor with this signature:
|
||||||
|
|
||||||
|
(SQLTester testContext, String[] argv, String content) throws Exception
|
||||||
|
|
||||||
|
argv is a list with the command name followed by any
|
||||||
|
arguments to that command. The argcCheck() method provides
|
||||||
|
very basic argc validation.
|
||||||
|
|
||||||
|
The content is any text content which was specified after the
|
||||||
|
command. Any command which does not permit content must pass that
|
||||||
|
argument to affirmNoContent() in their constructor.
|
||||||
|
|
||||||
|
Tests must throw on error.
|
||||||
|
*/
|
||||||
class Command {
|
class Command {
|
||||||
protected SQLTester tester;
|
protected SQLTester tester;
|
||||||
Command(SQLTester t){tester = t;}
|
Command(SQLTester t){tester = t;}
|
||||||
|
|
||||||
protected final void badArg(Object... msg){
|
protected final void toss(Class<? extends Exception> errorType, Object... msg) throws Exception {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s);
|
for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s);
|
||||||
throw new IllegalArgumentException(sb.toString());
|
final java.lang.reflect.Constructor<? extends Exception> ctor =
|
||||||
|
errorType.getConstructor(String.class);
|
||||||
|
throw ctor.newInstance(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void argcCheck(String[] argv, int min, int max){
|
protected final void toss(Object... msg) throws Exception{
|
||||||
|
toss(RuntimeException.class, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void badArg(Object... msg) throws Exception{
|
||||||
|
toss(IllegalArgumentException.class, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void argcCheck(String[] argv, int min, int max) throws Exception{
|
||||||
int argc = argv.length-1;
|
int argc = argv.length-1;
|
||||||
if(argc<min || argc>max){
|
if(argc<min || argc>max){
|
||||||
if( min==max ) badArg(argv[0],"requires exactly",min,"argument(s)");
|
if( min==max ) badArg(argv[0],"requires exactly",min,"argument(s)");
|
||||||
@ -134,11 +178,11 @@ class Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void argcCheck(String[] argv, int argc){
|
protected final void argcCheck(String[] argv, int argc) throws Exception{
|
||||||
argcCheck(argv, argc, argc);
|
argcCheck(argv, argc, argc);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void affirmNoContent(String content){
|
protected void affirmNoContent(String content) throws Exception{
|
||||||
if(null != content){
|
if(null != content){
|
||||||
badArg(this.getClass().getName(),"does not accept content.");
|
badArg(this.getClass().getName(),"does not accept content.");
|
||||||
}
|
}
|
||||||
@ -146,7 +190,7 @@ class Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DbCommand extends Command {
|
class DbCommand extends Command {
|
||||||
public DbCommand(SQLTester t, String[] argv, String content){
|
public DbCommand(SQLTester t, String[] argv, String content) throws Exception{
|
||||||
super(t);
|
super(t);
|
||||||
argcCheck(argv,1);
|
argcCheck(argv,1);
|
||||||
affirmNoContent(content);
|
affirmNoContent(content);
|
||||||
@ -155,7 +199,7 @@ class DbCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NullCommand extends Command {
|
class NullCommand extends Command {
|
||||||
public NullCommand(SQLTester t, String[] argv, String content){
|
public NullCommand(SQLTester t, String[] argv, String content) throws Exception{
|
||||||
super(t);
|
super(t);
|
||||||
argcCheck(argv,1);
|
argcCheck(argv,1);
|
||||||
affirmNoContent(content);
|
affirmNoContent(content);
|
||||||
@ -164,20 +208,28 @@ class NullCommand extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResultCommand extends Command {
|
class PrintCommand extends Command {
|
||||||
public ResultCommand(SQLTester t, String[] argv, String content){
|
public PrintCommand(SQLTester t, String[] argv, String content) throws Exception{
|
||||||
super(t);
|
super(t);
|
||||||
argcCheck(argv,0);
|
argcCheck(argv,0);
|
||||||
t.verbose(argv[0],"command is TODO");
|
t.outln(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResultCommand extends Command {
|
||||||
|
public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{
|
||||||
|
super(t);
|
||||||
|
argcCheck(argv,0);
|
||||||
|
//t.verbose(argv[0],"command is TODO");
|
||||||
t.incrementTestCounter();
|
t.incrementTestCounter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestCaseCommand extends Command {
|
class TestCaseCommand extends Command {
|
||||||
public TestCaseCommand(SQLTester t, String[] argv, String content){
|
public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{
|
||||||
super(t);
|
super(t);
|
||||||
argcCheck(argv,1);
|
argcCheck(argv,1);
|
||||||
t.verbose(argv[0],argv[1]);
|
//t.verbose(argv[0],argv[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +238,7 @@ class CommandDispatcher {
|
|||||||
switch(name){
|
switch(name){
|
||||||
case "db": return DbCommand.class;
|
case "db": return DbCommand.class;
|
||||||
case "null": return NullCommand.class;
|
case "null": return NullCommand.class;
|
||||||
|
case "print": return PrintCommand.class;
|
||||||
case "result": return ResultCommand.class;
|
case "result": return ResultCommand.class;
|
||||||
case "testcase": return TestCaseCommand.class;
|
case "testcase": return TestCaseCommand.class;
|
||||||
default: return null;
|
default: return null;
|
||||||
|
@ -90,7 +90,19 @@ class TestScript {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Chop script up into chunks containing individual commands and
|
Chop script up into chunks containing individual commands and
|
||||||
their inputs.
|
their inputs. The approach taken here is not as robust as
|
||||||
|
line-by-line parsing would be but the framework is structured
|
||||||
|
such that we could replace this part without unduly affecting the
|
||||||
|
evaluation bits. The potential problems with this approach
|
||||||
|
include:
|
||||||
|
|
||||||
|
- It's potentially possible that it will strip content out of a
|
||||||
|
testcase block.
|
||||||
|
|
||||||
|
- It loses all file location information, so we can't report line
|
||||||
|
numbers of errors.
|
||||||
|
|
||||||
|
If/when that becomes a problem, it can be refactored.
|
||||||
*/
|
*/
|
||||||
private List<String> chunkContent(){
|
private List<String> chunkContent(){
|
||||||
if( ignored ) return null;
|
if( ignored ) return null;
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
junk
|
junk
|
||||||
|
|
||||||
--null zilch
|
--null zilch
|
||||||
|
--print
|
||||||
|
This is from the print command.
|
||||||
--db 1
|
--db 1
|
||||||
--- also ignored
|
--- also ignored
|
||||||
--testcase first
|
--testcase first
|
||||||
|
18
manifest
18
manifest
@ -1,5 +1,5 @@
|
|||||||
C Add\smissing\slicense\sheader.\sMinor\scleanups\sin\sSQLTester.
|
C SQLTester:\sadd\sprint\scommand\sand\simprove\sargument\serror\sreporting\sinfrastructure.
|
||||||
D 2023-08-08T00:59:40.520
|
D 2023-08-08T09:45:33.280
|
||||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||||
@ -252,7 +252,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495
|
|||||||
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
|
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
|
||||||
F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
|
F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
|
||||||
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21b828cb61984977c7b2a0cbd803f0c15dd79ff524ab94d3ce52648c011b2c9c
|
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21b828cb61984977c7b2a0cbd803f0c15dd79ff524ab94d3ce52648c011b2c9c
|
||||||
F ext/jni/src/org/sqlite/jni/Tester1.java 55bf9c35c4a5649bdfb6ce940117d33ec24a6722bc252fadf7bc7102b9e94d6a
|
F ext/jni/src/org/sqlite/jni/Tester1.java 57404879fbea78f0b405b7643abb03dad0a6ce6cea9ec0c4ef55ea40267be565
|
||||||
F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee
|
F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee
|
||||||
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
|
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
|
||||||
F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
|
F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
|
||||||
@ -265,10 +265,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859
|
|||||||
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
|
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
|
||||||
F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a
|
F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a
|
||||||
F ext/jni/src/org/sqlite/jni/tester/Outer.java c35a54bd3fd3363ba2abb5533453454d8ffe3f942c9a37a7921c8f6739762e82
|
F ext/jni/src/org/sqlite/jni/tester/Outer.java c35a54bd3fd3363ba2abb5533453454d8ffe3f942c9a37a7921c8f6739762e82
|
||||||
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d89dc9921b41aea67ee3c69e763671467042b33b62085aa7a44444856c4eca3f
|
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1832399b73a1e246892149cbfad0ca5b8cf1ed69072322059fa9a14b2da2b1f1
|
||||||
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 6a631e2ecce24734bd93631ce00446ed109aaeb1ea6666f7a8aff74d96221a6a
|
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 38652e01cab9c07b20741829f54ef2f4a5c25a73b2c77213dd9198d4268acc51
|
||||||
F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3
|
F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3
|
||||||
F ext/jni/src/tests/000_first.test 0a066e5e30189545ca4f3586d45db6c08195a50bd2f00907b4d6a3727ff58c02
|
F ext/jni/src/tests/000_first.test 00b2347d4b974e67682859c292bc0d200788ab3f462eac922b8036f4e07114fc
|
||||||
F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd
|
F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd
|
||||||
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
|
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
|
||||||
F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
|
F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
|
||||||
@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
|||||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||||
P 9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28
|
P 5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2
|
||||||
R 75e2a8b50272267a155f3645b409db9d
|
R 8128e06f40a69e4ebe18ddcc9febcc5a
|
||||||
U stephan
|
U stephan
|
||||||
Z fbbe7908dd24ba8c5b8dfecd37f39fe7
|
Z d82fef86e57475799cbc6287645847ba
|
||||||
# Remove this line to create a well-formed Fossil manifest.
|
# Remove this line to create a well-formed Fossil manifest.
|
||||||
|
@ -1 +1 @@
|
|||||||
5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2
|
1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44
|
Reference in New Issue
Block a user