Make Most Unit Tests Independent of LibECL
This commit switches a set of OPM-Common's unit tests away from using direct calls to libecl functions and into using base types from OPM-Common itself (along with Boost.Filesystem). In particular summary related queries are replaced by calls to ESmry member functions (wrapped in libecl-like interfaces to minimise code changes). We disable checks on unit strings since ESmry currently does not have a way of associating those with individual variables.
This commit is contained in:
parent
bcfe700461
commit
c013639b51
66
tests/WorkArea.cpp
Normal file
66
tests/WorkArea.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2019 Equinor ASA.
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// NOTE: This file is inteded to be copy-pasted into user code
|
||||
// through an #include statement.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace {
|
||||
class WorkArea
|
||||
{
|
||||
public:
|
||||
explicit WorkArea(const std::string& subdir = "")
|
||||
: root_(boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path("wrk-%%%%"))
|
||||
, area_(root_)
|
||||
, orig_(boost::filesystem::current_path())
|
||||
{
|
||||
if (! subdir.empty())
|
||||
this->area_ /= subdir;
|
||||
|
||||
boost::filesystem::create_directories(this->area_);
|
||||
boost::filesystem::current_path(this->area_);
|
||||
}
|
||||
|
||||
void copyIn(const std::string& filename) const
|
||||
{
|
||||
boost::filesystem::copy_file(this->orig_ / filename,
|
||||
this->area_ / filename);
|
||||
}
|
||||
|
||||
void makeSubDir(const std::string& dirname)
|
||||
{
|
||||
boost::filesystem::create_directories(this->area_ / dirname);
|
||||
}
|
||||
|
||||
~WorkArea()
|
||||
{
|
||||
boost::filesystem::current_path(this->orig_);
|
||||
boost::filesystem::remove_all(this->root_);
|
||||
}
|
||||
|
||||
private:
|
||||
boost::filesystem::path root_;
|
||||
boost::filesystem::path area_;
|
||||
boost::filesystem::path orig_;
|
||||
};
|
||||
} // Anonymous
|
@ -17,22 +17,19 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE WTEST
|
||||
#define BOOST_TEST_MODULE MSIM_BASIC
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <ert/util/test_work_area.h>
|
||||
#include <ert/util/util.h>
|
||||
#include <ert/ecl/ecl_sum.hpp>
|
||||
#include <ert/ecl/ecl_file.hpp>
|
||||
#include <ert/ecl/ecl_file_view.hpp>
|
||||
#include <ert/ecl/ecl_rsthead.hpp>
|
||||
|
||||
|
||||
#include <opm/msim/msim.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <opm/io/eclipse/ERst.hpp>
|
||||
#include <opm/io/eclipse/ESmry.hpp>
|
||||
|
||||
#include <opm/output/data/Wells.hpp>
|
||||
#include <opm/output/eclipse/EclipseIO.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
@ -41,8 +38,11 @@
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
|
||||
|
||||
#include <tests/WorkArea.cpp>
|
||||
|
||||
using namespace Opm;
|
||||
|
||||
namespace {
|
||||
|
||||
double prod_opr(const EclipseState& es, const Schedule& /* sched */, const SummaryState&, const data::Solution& /* sol */, size_t /* report_step */, double seconds_elapsed) {
|
||||
const auto& units = es.getUnits();
|
||||
@ -59,6 +59,13 @@ void pressure(const EclipseState& es, const Schedule& /* sched */, data::Solutio
|
||||
std::fill(data.begin(), data.end(), units.to_si(UnitSystem::measure::pressure, seconds_elapsed));
|
||||
}
|
||||
|
||||
bool is_file(const boost::filesystem::path& name)
|
||||
{
|
||||
return boost::filesystem::exists(name)
|
||||
&& boost::filesystem::is_regular_file(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(RUN) {
|
||||
Parser parser;
|
||||
@ -71,45 +78,35 @@ BOOST_AUTO_TEST_CASE(RUN) {
|
||||
msim.well_rate("PROD", data::Rates::opt::oil, prod_opr);
|
||||
msim.solution("PRESSURE", pressure);
|
||||
{
|
||||
test_work_area_type * work_area = test_work_area_alloc("test_msim");
|
||||
const WorkArea work_area("test_msim");
|
||||
EclipseIO io(state, state.getInputGrid(), schedule, summary_config);
|
||||
|
||||
msim.run(schedule, io, false);
|
||||
|
||||
for (const auto& fname : {"SPE1CASE1.INIT", "SPE1CASE1.UNRST", "SPE1CASE1.EGRID", "SPE1CASE1.SMSPEC", "SPE1CASE1.UNSMRY"})
|
||||
BOOST_CHECK( util_is_file( fname ));
|
||||
BOOST_CHECK( is_file( fname ));
|
||||
|
||||
{
|
||||
ecl_sum_type * ecl_sum = ecl_sum_fread_alloc_case("SPE1CASE1", ":");
|
||||
int param_index = ecl_sum_get_general_var_params_index(ecl_sum, "WOPR:PROD");
|
||||
for (int time_index=0; time_index < ecl_sum_get_data_length(ecl_sum); time_index++) {
|
||||
double seconds_elapsed = ecl_sum_iget_sim_days(ecl_sum, time_index) * 86400;
|
||||
double opr = ecl_sum_iget(ecl_sum, time_index, param_index);
|
||||
BOOST_CHECK_CLOSE(seconds_elapsed, opr, 1e-3);
|
||||
const auto smry = EclIO::ESmry("SPE1CASE1");
|
||||
const auto& time = smry.get("TIME");
|
||||
const auto& press = smry.get("WOPR:PROD");
|
||||
|
||||
for (auto nstep = time.size(), time_index=0*nstep; time_index < nstep; time_index++) {
|
||||
double seconds_elapsed = time[time_index] * 86400;
|
||||
BOOST_CHECK_CLOSE(seconds_elapsed, press[time_index], 1e-3);
|
||||
}
|
||||
ecl_sum_free( ecl_sum );
|
||||
}
|
||||
|
||||
{
|
||||
int report_step = 1;
|
||||
ecl_file_type * rst_file = ecl_file_open("SPE1CASE1.UNRST", 0);
|
||||
ecl_file_view_type * global_view = ecl_file_get_global_view( rst_file );
|
||||
while (true) {
|
||||
ecl_file_view_type * rst_view = ecl_file_view_add_restart_view( global_view, -1, report_step, -1, -1);
|
||||
if (!rst_view)
|
||||
break;
|
||||
auto rst = EclIO::ERst("SPE1CASE1.UNRST");
|
||||
|
||||
{
|
||||
ecl_rsthead_type * rst_head = ecl_rsthead_alloc( rst_view, report_step);
|
||||
const ecl_kw_type * p = ecl_file_view_iget_named_kw(rst_view, "PRESSURE", 0);
|
||||
BOOST_CHECK_CLOSE( ecl_kw_iget_float(p, 0), rst_head->sim_days * 86400, 1e-3 );
|
||||
ecl_rsthead_free( rst_head );
|
||||
}
|
||||
report_step++;
|
||||
for (const auto& step : rst.listOfReportStepNumbers()) {
|
||||
const auto& dh = rst.getRst<double>("DOUBHEAD", step);
|
||||
const auto& press = rst.getRst<float>("PRESSURE", step);
|
||||
|
||||
// DOUBHEAD[0] is elapsed time in days since start of simulation.
|
||||
BOOST_CHECK_CLOSE( press[0], dh[0] * 86400, 1e-3 );
|
||||
}
|
||||
ecl_file_close(rst_file);
|
||||
}
|
||||
|
||||
test_work_area_free( work_area );
|
||||
}
|
||||
}
|
||||
|
@ -17,16 +17,14 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#define BOOST_TEST_MODULE ACTIONX_SIM
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <ert/util/test_work_area.h>
|
||||
#include <ert/ecl/ecl_sum.hpp>
|
||||
#include <opm/msim/msim.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
|
||||
@ -40,13 +38,15 @@
|
||||
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
|
||||
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
|
||||
|
||||
#include <opm/io/eclipse/ESmry.hpp>
|
||||
|
||||
#include <opm/output/eclipse/EclipseIO.hpp>
|
||||
|
||||
#include <opm/msim/msim.hpp>
|
||||
#include <tests/WorkArea.cpp>
|
||||
|
||||
using namespace Opm;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct test_data {
|
||||
Deck deck;
|
||||
@ -118,6 +118,33 @@ double inj_wir_INJ(const EclipseState& , const Schedule& sched, const SummarySta
|
||||
return -99;
|
||||
}
|
||||
|
||||
bool ecl_sum_has_general_var(const EclIO::ESmry& smry, const std::string& var)
|
||||
{
|
||||
return smry.hasKey(var);
|
||||
}
|
||||
|
||||
float ecl_sum_get_general_var(const EclIO::ESmry& smry, const int timeIdx, const std::string& var)
|
||||
{
|
||||
return smry.get(var)[timeIdx];
|
||||
}
|
||||
|
||||
int ecl_sum_get_data_length(const EclIO::ESmry& smry)
|
||||
{
|
||||
return static_cast<int>(smry.get("TIME").size());
|
||||
}
|
||||
|
||||
int ecl_sum_get_last_report_step(const EclIO::ESmry& smry)
|
||||
{
|
||||
return static_cast<int>(smry.get_at_rstep("TIME").size());
|
||||
}
|
||||
|
||||
int ecl_sum_iget_report_end(const EclIO::ESmry& smry, const int reportStep)
|
||||
{
|
||||
return smry.timestepIdxAtReportstepStart(reportStep + 1) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The deck tested here has a UDQ DEFINE statement which sorts the wells after
|
||||
oil production rate, and then subsequently closes the well with lowest OPR
|
||||
@ -130,7 +157,7 @@ BOOST_AUTO_TEST_CASE(UDQ_SORTA_EXAMPLE) {
|
||||
test_data td( actionx );
|
||||
msim sim(td.state);
|
||||
{
|
||||
test_work_area_type * work_area = test_work_area_alloc("test_msim");
|
||||
WorkArea work_area("test_msim");
|
||||
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
|
||||
|
||||
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
|
||||
@ -151,8 +178,6 @@ BOOST_AUTO_TEST_CASE(UDQ_SORTA_EXAMPLE) {
|
||||
BOOST_CHECK(w1.getStatus() == Well2::Status::OPEN );
|
||||
BOOST_CHECK(w4.getStatus() == Well2::Status::SHUT );
|
||||
}
|
||||
|
||||
test_work_area_free(work_area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +188,7 @@ BOOST_AUTO_TEST_CASE(WELL_CLOSE_EXAMPLE) {
|
||||
test_data td( actionx1 );
|
||||
msim sim(td.state);
|
||||
{
|
||||
test_work_area_type * work_area = test_work_area_alloc("test_msim");
|
||||
WorkArea work_area("test_msim");
|
||||
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
|
||||
|
||||
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
|
||||
@ -208,8 +233,6 @@ BOOST_AUTO_TEST_CASE(WELL_CLOSE_EXAMPLE) {
|
||||
BOOST_CHECK(w4_10.getStatus() == Well2::Status::OPEN );
|
||||
BOOST_CHECK(w4_11.getStatus() == Well2::Status::SHUT );
|
||||
}
|
||||
|
||||
test_work_area_free(work_area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +242,7 @@ BOOST_AUTO_TEST_CASE(UDQ_ASSIGN) {
|
||||
test_data td( actionx1 );
|
||||
msim sim(td.state);
|
||||
{
|
||||
test_work_area_type * work_area = test_work_area_alloc("test_msim");
|
||||
WorkArea work_area("test_msim");
|
||||
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
|
||||
|
||||
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
|
||||
@ -236,14 +259,16 @@ BOOST_AUTO_TEST_CASE(UDQ_ASSIGN) {
|
||||
|
||||
const auto& base_name = td.state.getIOConfig().getBaseName();
|
||||
|
||||
ecl_sum_type * ecl_sum = ecl_sum_fread_alloc_case( base_name.c_str(), ":");
|
||||
const EclIO::ESmry ecl_sum(base_name + ".SMSPEC");
|
||||
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, "WUBHP:P1") );
|
||||
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, "WUBHP:P2") );
|
||||
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, "WUOPRL:P3") );
|
||||
BOOST_CHECK( ecl_sum_has_general_var(ecl_sum, "WUOPRL:P4") );
|
||||
|
||||
#if 0
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_unit(ecl_sum, "WUBHP:P1"), "BARSA");
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_unit(ecl_sum, "WUOPRL:P1"), "SM3/DAY");
|
||||
#endif
|
||||
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, 1, "WUBHP:P1"), 11);
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, 1, "WUBHP:P2"), 12);
|
||||
@ -254,9 +279,6 @@ BOOST_AUTO_TEST_CASE(UDQ_ASSIGN) {
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, 1, "WUOPRL:P2"), 20);
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, 1, "WUOPRL:P3"), 20);
|
||||
BOOST_CHECK_EQUAL( ecl_sum_get_general_var(ecl_sum, 1, "WUOPRL:P4"), 20);
|
||||
ecl_sum_free( ecl_sum );
|
||||
|
||||
test_work_area_free(work_area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +288,7 @@ BOOST_AUTO_TEST_CASE(UDQ_WUWCT) {
|
||||
test_data td( actionx1 );
|
||||
msim sim(td.state);
|
||||
{
|
||||
test_work_area_type * work_area = test_work_area_alloc("test_msim");
|
||||
WorkArea work_area("test_msim");
|
||||
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
|
||||
|
||||
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
|
||||
@ -282,7 +304,7 @@ BOOST_AUTO_TEST_CASE(UDQ_WUWCT) {
|
||||
sim.run(td.schedule, io, false);
|
||||
|
||||
const auto& base_name = td.state.getIOConfig().getBaseName();
|
||||
ecl_sum_type * ecl_sum = ecl_sum_fread_alloc_case( base_name.c_str(), ":");
|
||||
const EclIO::ESmry ecl_sum(base_name + ".SMSPEC");
|
||||
|
||||
for (int step = 0; step < ecl_sum_get_data_length(ecl_sum); step++) {
|
||||
double wopr_sum = 0;
|
||||
@ -300,9 +322,6 @@ BOOST_AUTO_TEST_CASE(UDQ_WUWCT) {
|
||||
ecl_sum_get_general_var(ecl_sum, step, "FUOPR"));
|
||||
BOOST_CHECK_EQUAL( wopr_sum, ecl_sum_get_general_var(ecl_sum, step, "FOPR"));
|
||||
}
|
||||
|
||||
|
||||
test_work_area_free(work_area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,15 +338,16 @@ BOOST_AUTO_TEST_CASE(UDA) {
|
||||
sim.well_rate("P4", data::Rates::opt::wat, prod_wpr_P4);
|
||||
sim.well_rate("INJ", data::Rates::opt::wat, inj_wir_INJ);
|
||||
{
|
||||
ecl::util::TestArea ta("uda_sim");
|
||||
WorkArea work_area("uda_sim");
|
||||
|
||||
sim.run(td.schedule, io, true);
|
||||
|
||||
const auto& base_name = td.state.getIOConfig().getBaseName();
|
||||
ecl_sum_type * ecl_sum = ecl_sum_fread_alloc_case( base_name.c_str(), ":");
|
||||
const EclIO::ESmry ecl_sum(base_name + ".SMSPEC");
|
||||
|
||||
// Should only get at report steps
|
||||
for (int report_step = 2; report_step < ecl_sum_get_last_report_step(ecl_sum); report_step++) {
|
||||
const auto last_report = ecl_sum_get_last_report_step(ecl_sum);
|
||||
for (int report_step = 2; report_step < last_report; report_step++) {
|
||||
double wwpr_sum = 0;
|
||||
{
|
||||
int prev_tstep = ecl_sum_iget_report_end(ecl_sum, report_step - 1);
|
||||
@ -338,7 +358,5 @@ BOOST_AUTO_TEST_CASE(UDA) {
|
||||
}
|
||||
BOOST_CHECK_CLOSE( 0.90 * wwpr_sum, ecl_sum_get_general_var(ecl_sum, ecl_sum_iget_report_end(ecl_sum, report_step), "WWIR:INJ"), 1e-3);
|
||||
}
|
||||
|
||||
ecl_sum_free( ecl_sum );
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,6 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include <ert/util/util.h>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
|
||||
|
@ -30,8 +30,6 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
//#include <ert/util/test_work_area.h>
|
||||
|
||||
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
|
||||
|
@ -17,11 +17,7 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#define BOOST_TEST_MODULE InitConfigTests
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
@ -31,7 +27,7 @@
|
||||
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
|
||||
#include <ert/util/test_work_area.hpp>
|
||||
#include <tests/WorkArea.cpp>
|
||||
|
||||
using namespace Opm;
|
||||
|
||||
@ -188,8 +184,10 @@ BOOST_AUTO_TEST_CASE( EquilOperations ) {
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(RestartCWD) {
|
||||
test_work_area_type * work_area = test_work_area_alloc("restart_cwd");
|
||||
mkdir("simulation", 0777);
|
||||
WorkArea output_area;
|
||||
|
||||
output_area.makeSubDir("simulation");
|
||||
|
||||
{
|
||||
std::fstream fs;
|
||||
fs.open ("simulation/CASE.DATA", std::fstream::out);
|
||||
@ -229,8 +227,6 @@ BOOST_AUTO_TEST_CASE(RestartCWD) {
|
||||
Opm::InitConfig init_config(deck);
|
||||
BOOST_CHECK_EQUAL(init_config.getRestartRootName(), "/abs/path/BASE");
|
||||
}
|
||||
|
||||
test_work_area_free(work_area);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
@ -21,8 +21,6 @@ along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <ert/ecl/ecl_util.h>
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
|
||||
#include <opm/parser/eclipse/Parser/Parser.hpp>
|
||||
@ -60,6 +58,10 @@ BOOST_AUTO_TEST_CASE(TwoPhase) {
|
||||
BOOST_CHECK( !phases.active( Phase::GAS ) );
|
||||
BOOST_CHECK( phases.active( Phase::WATER ) );
|
||||
BOOST_CHECK( !phases.active( Phase::POLYMW ) );
|
||||
|
||||
const auto ECL_OIL_PHASE = 1 << 0;
|
||||
const auto ECL_WATER_PHASE = 1 << 2;
|
||||
|
||||
BOOST_CHECK_EQUAL( ECL_OIL_PHASE + ECL_WATER_PHASE , runspec.eclPhaseMask( ));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user