mirror of
https://github.com/lexborisov/Modest
synced 2024-11-22 05:41:32 +03:00
Added test command for make; Added tests for declarations: test/mycss/data/declaration
This commit is contained in:
parent
402e3f6e1a
commit
4bc77fb8e8
8
Makefile
8
Makefile
@ -1,5 +1,6 @@
|
|||||||
TARGET := source
|
TARGET := source
|
||||||
SRCDIR := source
|
SRCDIR := source
|
||||||
|
TSTDIR := test
|
||||||
|
|
||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ endif
|
|||||||
|
|
||||||
SRCS :=
|
SRCS :=
|
||||||
HDRS :=
|
HDRS :=
|
||||||
EXTDIRS := examples
|
EXTDIRS := examples test
|
||||||
|
|
||||||
all: create shared static
|
all: create shared static
|
||||||
for f in $(EXTDIRS); do $(MAKE) -C $$f all; done
|
for f in $(EXTDIRS); do $(MAKE) -C $$f all; done
|
||||||
@ -74,4 +75,7 @@ clone: create clean_include myhtml_clone mycss_clone modest_clone myfont_clone
|
|||||||
find include -name "*.h" -exec sed -i '.bak' -E 's/^[ \t]*#[ \t]*include[ \t]*"([^"]+)"/#include <\1>/g' {} \;
|
find include -name "*.h" -exec sed -i '.bak' -E 's/^[ \t]*#[ \t]*include[ \t]*"([^"]+)"/#include <\1>/g' {} \;
|
||||||
find include -name "*.h.bak" -exec rm -f {} \;
|
find include -name "*.h.bak" -exec rm -f {} \;
|
||||||
|
|
||||||
.PHONY: all clean clone
|
test:
|
||||||
|
test/mycss/declaration test/mycss/data/declaration
|
||||||
|
|
||||||
|
.PHONY: all clean clone test
|
50
test/Makefile
Normal file
50
test/Makefile
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
TARGET := test
|
||||||
|
SRCDIR := test
|
||||||
|
|
||||||
|
CC ?= gcc
|
||||||
|
LIBS := ../lib/libmodest_static.a
|
||||||
|
LDLIBS := $(LIBS)
|
||||||
|
|
||||||
|
INCLUDE_TMP := include
|
||||||
|
|
||||||
|
MODEST_OPTIMIZATION_LEVEL ?= -O2
|
||||||
|
|
||||||
|
CFLAGS ?= -Wall -Werror
|
||||||
|
CFLAGS += $(MODEST_OPTIMIZATION_LEVEL) -Wno-unused-variable -fPIC --std=c99 -I../include
|
||||||
|
|
||||||
|
MODEST_BUILD_WITHOUT_THREADS ?= NO
|
||||||
|
ifeq ($(MODEST_BUILD_WITHOUT_THREADS),YES)
|
||||||
|
$(info Build Examples without POSIX Threads)
|
||||||
|
else
|
||||||
|
$(info Build Examples with POSIX Threads)
|
||||||
|
CFLAGS += -pthread
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
else
|
||||||
|
UNAM := $(shell uname -s)
|
||||||
|
ifeq ($(UNAM),Darwin)
|
||||||
|
else
|
||||||
|
CFLAGS += -D_POSIX_C_SOURCE=199309L
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
find_files_h = $(wildcard $(dir)/*.h)
|
||||||
|
find_files_c = $(wildcard $(dir)/*.c)
|
||||||
|
|
||||||
|
SUBDIRS := mycss
|
||||||
|
HDRS += $(foreach dir,$(SUBDIRS),$(find_files_h))
|
||||||
|
SRCS += $(foreach dir,$(SUBDIRS),$(find_files_c))
|
||||||
|
#SRCS_FN += $(foreach dir,$(SUBDIRS),$(notdir $(find_files_c)))
|
||||||
|
|
||||||
|
OBJS := $(patsubst %.c,%,$(SRCS))
|
||||||
|
#OBJS_RM := $(patsubst %.c,%,$(SRCS_FN))
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJS)
|
||||||
|
|
||||||
|
.PHONY: all
|
2
test/mycss/data/declaration/height.html
Normal file
2
test/mycss/data/declaration/height.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<test name="height" value="10%">10%</test>
|
||||||
|
<test name="height" value="unset">unset</test>
|
2
test/mycss/data/declaration/width.html
Normal file
2
test/mycss/data/declaration/width.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<test name="width" value="10%">10%</test>
|
||||||
|
<test name="width" value="unset">unset</test>
|
399
test/mycss/declaration.c
Normal file
399
test/mycss/declaration.c
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 2015-2016 Alexander Borisov
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Author: lex.borisov@gmail.com (Alexander Borisov)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <myhtml/myhtml.h>
|
||||||
|
#include <myhtml/mystring.h>
|
||||||
|
#include <myhtml/serialization.h>
|
||||||
|
|
||||||
|
#include <mycss/mycss.h>
|
||||||
|
#include <mycss/declaration/init.h>
|
||||||
|
|
||||||
|
#define TEST_FAILED(status) ((status) != 0)
|
||||||
|
|
||||||
|
typedef bool (*test_read_dir_callback_f)(const char* filename, size_t filename_length, void* ctx);
|
||||||
|
|
||||||
|
struct test_stat {
|
||||||
|
size_t good;
|
||||||
|
size_t total;
|
||||||
|
}
|
||||||
|
typedef test_stat_t;
|
||||||
|
|
||||||
|
struct test_data {
|
||||||
|
myhtml_tree_t* tree;
|
||||||
|
mycss_entry_t* entry;
|
||||||
|
|
||||||
|
test_stat_t stat;
|
||||||
|
}
|
||||||
|
typedef test_data_t;
|
||||||
|
|
||||||
|
struct test_res {
|
||||||
|
char* data;
|
||||||
|
size_t size;
|
||||||
|
}
|
||||||
|
typedef test_res_t;
|
||||||
|
|
||||||
|
test_res_t test_load_file(const char* filename)
|
||||||
|
{
|
||||||
|
FILE *fh = fopen(filename, "rb");
|
||||||
|
if(fh == NULL) {
|
||||||
|
fprintf(stderr, "Can't open file: %s\n", filename);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(fh, 0L, SEEK_END);
|
||||||
|
long size = ftell(fh);
|
||||||
|
fseek(fh, 0L, SEEK_SET);
|
||||||
|
|
||||||
|
char *file_data = (char*)malloc(size + 1);
|
||||||
|
if(file_data == NULL) {
|
||||||
|
fprintf(stderr, "Can't allocate mem for file: %s\n", filename);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nread = fread(file_data, 1, size, fh);
|
||||||
|
if (nread != size) {
|
||||||
|
fprintf(stderr, "Could not read %ld bytes (%zu bytes done)\n", size, nread);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fh);
|
||||||
|
|
||||||
|
if(size < 0) {
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (test_res_t){file_data, (size_t)size};
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_init_str_raw(myhtml_string_raw_t* str, size_t size)
|
||||||
|
{
|
||||||
|
str->size = size;
|
||||||
|
str->length = 0;
|
||||||
|
str->data = (char*)myhtml_malloc(str->size * sizeof(char));
|
||||||
|
|
||||||
|
if(str->data == NULL) {
|
||||||
|
fprintf(stderr, "Can't allocation resources for myhtml_string_raw_t object\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_reallocate_str(myhtml_string_raw_t *str, size_t size)
|
||||||
|
{
|
||||||
|
str->data = (char*)myhtml_realloc(str->data, size * sizeof(char));
|
||||||
|
|
||||||
|
if(str->data == NULL) {
|
||||||
|
fprintf(stderr, "Can't reallocation resources for myhtml_string_raw_t object\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_str_set_term(myhtml_string_raw_t *str)
|
||||||
|
{
|
||||||
|
if((str->length + 1) >= str->size)
|
||||||
|
test_reallocate_str(str, 64);
|
||||||
|
|
||||||
|
str->data[ str->length ] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
myhtml_string_raw_t test_combine_to_style(const char* one, size_t one_len, const char* two, size_t two_len)
|
||||||
|
{
|
||||||
|
size_t new_size = one_len + two_len;
|
||||||
|
|
||||||
|
myhtml_string_raw_t str = {0};
|
||||||
|
test_init_str_raw(&str, (new_size + 2));
|
||||||
|
|
||||||
|
memcpy(str.data, one, sizeof(char) * one_len);
|
||||||
|
str.length = one_len;
|
||||||
|
|
||||||
|
str.data[str.length] = ':';
|
||||||
|
str.length++;
|
||||||
|
|
||||||
|
memcpy(&str.data[str.length], two, sizeof(char) * two_len);
|
||||||
|
str.length += two_len;
|
||||||
|
|
||||||
|
str.data[ str.length ] = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* basic init */
|
||||||
|
myhtml_tree_t * test_declaration_create_myhtml(void)
|
||||||
|
{
|
||||||
|
myhtml_t* myhtml = myhtml_create();
|
||||||
|
if(myhtml == NULL)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
myhtml_status_t status = myhtml_init(myhtml, MyHTML_OPTIONS_PARSE_MODE_SINGLE, 1, 0);
|
||||||
|
if(TEST_FAILED(status)) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
myhtml_tree_t* tree = myhtml_tree_create();
|
||||||
|
if(tree == NULL)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
status = myhtml_tree_init(tree, myhtml);
|
||||||
|
if(TEST_FAILED(status)) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
myhtml_tree_parse_flags_set(tree, MyHTML_TREE_PARSE_FLAGS_SKIP_WHITESPACE_TOKEN|MyHTML_TREE_PARSE_FLAGS_WITHOUT_DOCTYPE_IN_TREE);
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_declaration_destroy_myhtml(myhtml_tree_t *tree)
|
||||||
|
{
|
||||||
|
myhtml_t* myhtml = tree->myhtml;
|
||||||
|
|
||||||
|
myhtml_tree_destroy(tree);
|
||||||
|
myhtml_destroy(myhtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
mycss_entry_t * test_declaration_create_mycss(void)
|
||||||
|
{
|
||||||
|
mycss_t *mycss = mycss_create();
|
||||||
|
if(mycss == NULL)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
mycss_status_t status = mycss_init(mycss);
|
||||||
|
if(TEST_FAILED(status)) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
// currenr entry work init
|
||||||
|
mycss_entry_t *entry = mycss_entry_create();
|
||||||
|
if(entry == NULL)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
status = mycss_entry_init(mycss, entry);
|
||||||
|
if(TEST_FAILED(status)) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_declaration_destroy_mycss(mycss_entry_t *entry)
|
||||||
|
{
|
||||||
|
mycss_t *mycss = entry->mycss;
|
||||||
|
|
||||||
|
mycss_entry_destroy(entry, true);
|
||||||
|
mycss_destroy(mycss, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_stat_t test_read_dir(const char* dir_path, test_read_dir_callback_f callback, void* context)
|
||||||
|
{
|
||||||
|
/* ho-ho-ho */
|
||||||
|
char path[(4096 * 4)];
|
||||||
|
sprintf(path, "%s", dir_path);
|
||||||
|
|
||||||
|
printf("Tests in directory: %s\n", path);
|
||||||
|
|
||||||
|
size_t path_len = strlen(dir_path);
|
||||||
|
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *ent;
|
||||||
|
struct stat path_stat;
|
||||||
|
|
||||||
|
test_stat_t result_stat = {0};
|
||||||
|
|
||||||
|
if((dir = opendir(dir_path)) != NULL)
|
||||||
|
{
|
||||||
|
while((ent = readdir(dir)) != NULL)
|
||||||
|
{
|
||||||
|
sprintf(&path[path_len], "/%s", ent->d_name);
|
||||||
|
|
||||||
|
stat(path, &path_stat);
|
||||||
|
|
||||||
|
if(ent->d_name[0] == '.' || S_ISDIR(path_stat.st_mode))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result_stat.total++;
|
||||||
|
printf("%zu) %s: ", result_stat.total, ent->d_name);
|
||||||
|
|
||||||
|
if(callback(path, (ent->d_namlen + path_len + 1), context)) {
|
||||||
|
result_stat.good++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir (dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test */
|
||||||
|
bool test_process_elements(test_data_t *test_data, myhtml_collection_t *collection, test_stat_t* result_stat);
|
||||||
|
myhtml_string_raw_t test_process_result_from_node(test_data_t *test_data, myhtml_tree_node_t *node);
|
||||||
|
myhtml_string_raw_t test_process_serialize_stype(test_data_t *test_data, const char* style, size_t style_size);
|
||||||
|
void test_process_serialize_callback(const char* buffer, size_t size, void* ctx);
|
||||||
|
|
||||||
|
bool test_process_callback(const char* filename, size_t filename_len, void* context)
|
||||||
|
{
|
||||||
|
test_data_t *test_data = (test_data_t*)context;
|
||||||
|
test_res_t html_data = test_load_file(filename);
|
||||||
|
test_stat_t result_stat = {0};
|
||||||
|
|
||||||
|
/* parse html */
|
||||||
|
myhtml_status_t status = myhtml_parse(test_data->tree, MyHTML_ENCODING_UTF_8, html_data.data, html_data.size);
|
||||||
|
if(status) {
|
||||||
|
fprintf(stderr, "Could parse HTML from file %s error code %d\n", filename, status);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find test elements */
|
||||||
|
myhtml_collection_t *collection = myhtml_get_nodes_by_name_in_scope(test_data->tree, NULL, test_data->tree->node_body, "test", 4, NULL);
|
||||||
|
|
||||||
|
if(collection->length)
|
||||||
|
test_process_elements(test_data, collection, &result_stat);
|
||||||
|
|
||||||
|
myhtml_collection_destroy(collection);
|
||||||
|
|
||||||
|
if((result_stat.total - result_stat.good)) {
|
||||||
|
printf("\tResult: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("count(%zu) good(%zu) bad(%zu)\n", result_stat.total, result_stat.good, (result_stat.total - result_stat.good));
|
||||||
|
|
||||||
|
test_data->stat.good += result_stat.good;
|
||||||
|
test_data->stat.total += result_stat.total;
|
||||||
|
|
||||||
|
myhtml_free(html_data.data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_process_elements(test_data_t *test_data, myhtml_collection_t *collection, test_stat_t* result_stat)
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < collection->length; i++)
|
||||||
|
{
|
||||||
|
myhtml_tree_node_t *node = collection->list[i];
|
||||||
|
myhtml_tree_attr_t* attr_name = myhtml_attribute_by_key(node, "name", 4);
|
||||||
|
myhtml_tree_attr_t* attr_value = myhtml_attribute_by_key(node, "value", 5);
|
||||||
|
|
||||||
|
size_t name_length;
|
||||||
|
size_t value_length;
|
||||||
|
|
||||||
|
const char* name = myhtml_attribute_value(attr_name, &name_length);
|
||||||
|
const char* value = myhtml_attribute_value(attr_value, &value_length);
|
||||||
|
|
||||||
|
myhtml_string_raw_t style = test_combine_to_style(name, name_length, value, value_length);
|
||||||
|
|
||||||
|
myhtml_string_raw_t correct_result = test_process_result_from_node(test_data, node);
|
||||||
|
myhtml_string_raw_t parse_result = test_process_serialize_stype(test_data, style.data, style.length);
|
||||||
|
|
||||||
|
result_stat->total++;
|
||||||
|
|
||||||
|
if(correct_result.length == parse_result.length &&
|
||||||
|
strcmp(correct_result.data, parse_result.data) == 0)
|
||||||
|
{
|
||||||
|
result_stat->good++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if((result_stat->total - result_stat->good) == 1) {
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\tBad: name=\"%s\" value=\"%s\" need=\"%s\" result=\"%s\"\n", name, value, correct_result.data, parse_result.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
myhtml_string_raw_destroy(&style, false);
|
||||||
|
myhtml_string_raw_destroy(&correct_result, false);
|
||||||
|
myhtml_string_raw_destroy(&parse_result, false);
|
||||||
|
|
||||||
|
mycss_entry_clean_all(test_data->entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
myhtml_string_raw_t test_process_result_from_node(test_data_t *test_data, myhtml_tree_node_t *node)
|
||||||
|
{
|
||||||
|
myhtml_string_raw_t str = {0};
|
||||||
|
|
||||||
|
if(myhtml_serialization_node_buffer(test_data->tree, node->child, &str) == false) {
|
||||||
|
fprintf(stderr, "Could serialization HTML node\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
myhtml_string_raw_t test_process_serialize_stype(test_data_t *test_data, const char* style, size_t style_size)
|
||||||
|
{
|
||||||
|
myhtml_string_raw_t str;
|
||||||
|
test_init_str_raw(&str, 4096);
|
||||||
|
|
||||||
|
mycss_status_t status;
|
||||||
|
mycss_declaration_entry_t *declaration = mycss_declaration_parse(test_data->entry->declaration, MyHTML_ENCODING_UTF_8, style, style_size, &status);
|
||||||
|
|
||||||
|
if(status) {
|
||||||
|
fprintf(stderr, "Could parse CSS Style (%d): %s\n", status, style);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mycss_declaration_serialization_entry_only_value(test_data->entry, declaration, test_process_serialize_callback, &str);
|
||||||
|
test_str_set_term(&str);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_process_serialize_callback(const char* buffer, size_t size, void* ctx)
|
||||||
|
{
|
||||||
|
myhtml_string_raw_t *str = (myhtml_string_raw_t*)ctx;
|
||||||
|
|
||||||
|
if((str->length + size) >= str->size)
|
||||||
|
test_reallocate_str(str, 4096);
|
||||||
|
|
||||||
|
memcpy(&str->data[ str->length ], buffer, sizeof(char) * size);
|
||||||
|
str->length += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char * argv[])
|
||||||
|
{
|
||||||
|
setbuf(stdout, NULL);
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("Bad ARGV!\nUse: declaration <path_to_dir_test>\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_data_t test_data = {
|
||||||
|
test_declaration_create_myhtml(),
|
||||||
|
test_declaration_create_mycss(),
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
//test_read_dir("/new/C-git/Modest/test/mycss/data/declaration", test_process_callback, &test_data);
|
||||||
|
|
||||||
|
test_read_dir(argv[1], test_process_callback, &test_data);
|
||||||
|
|
||||||
|
size_t bad_count = (test_data.stat.total - test_data.stat.good);
|
||||||
|
printf("\nTotal: %zu; Good: %zu; Bad: %zu\n", test_data.stat.total, test_data.stat.good, bad_count);
|
||||||
|
|
||||||
|
test_declaration_destroy_myhtml(test_data.tree);
|
||||||
|
test_declaration_destroy_mycss(test_data.entry);
|
||||||
|
|
||||||
|
return (bad_count ? EXIT_FAILURE : EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user