#include "FluidSim/Machine.hpp" #include "FluidSim/ScenarioCatalog.hpp" #include #include #include #include #include #include #include using namespace fluidsim::port; namespace { void check(bool value,const char* why){if(!value)throw std::runtime_error(why);} templatevoid rejects(F&& operation,const char* why){bool rejected=false;try{operation();}catch(const std::exception&){rejected=true;}check(rejected,why);} struct TempDirectory { std::filesystem::path path=std::filesystem::temp_directory_path()/("fluidsim-port-tests-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); TempDirectory(){std::filesystem::create_directories(path);} ~TempDirectory(){std::error_code error;std::filesystem::remove_all(path,error);} }; void write(const std::filesystem::path& file,const std::string& value){std::ofstream stream(file);stream.exceptions(std::ios::failbit|std::ios::badbit);stream<(stream)),{}};} MachinePortDescriptor signal(PortDomain domain,PortDirection direction,SignalQuantity quantity=SignalQuantity::None){MachinePortDescriptor port;port.key="value";port.name="Value";port.domain=domain;port.direction=direction;port.quantity=quantity;port.cardinality=direction==PortDirection::Output?PortCardinality::Multiple:PortCardinality::Single;return port;} } int main()try{ MachinePart undeclared;undeclared.kind=MachinePartKind(machine_part_kind_count); rejects([&]{machine_port_count(undeclared);},"Undeclared part silently acquired gas ports"); rejects([&]{machine_part_volume(undeclared);},"Undeclared part silently acquired gas storage"); for(int kind=0;kind keys; for(unsigned i=0;i::quiet_NaN();check(SignalValue::number(nan).validity==SignalValidity::Fault&&!signal_value_valid_for(SignalValue::number(1,nan),number_in),"Nonfinite signal was treated as valid"); for(auto domain:{PortDomain::Gas,PortDomain::Rotational,PortDomain::Translational}){MachinePortDescriptor a;a.key="physical";a.name="Physical";a.domain=domain;validate_port_descriptor(a);check(port_compatibility(a,a).allowed,"Physical bidirectional domain was rejected");check(!port_compatibility(a,boolean_in).allowed,"Signal connected to a physical port");} check(std::string(signal_quantity_unit(SignalQuantity::AngularSpeed))=="rad/s","Internal speed units are not SI"); TempDirectory temp;const auto file=temp.path/"ports.machine.toml"; const std::string legacy=R"(format = 4 name = 'Legacy control' [[parts]] id = 1 kind = 'ecu' [[parts]] id = 2 kind = 'cylinder' [[parts]] id = 3 kind = 'throttle' [[connections]] a_part = 1 a_port = 0 b_part = 2 b_port = 2 method = 4 [[connections]] a_part = 1 a_port = 0 b_part = 3 b_port = 2 method = 4 )"; write(file,legacy);auto d=load_machine(file);check(std::count_if(d.links.begin(),d.links.end(),[](const auto& link){return link.kind==MachineConnectionKind::Control;})==2,"Legacy ECU fan-out was lost"); check(machine_link_domain(d,d.links[0])==PortDomain::EcuAssignment,"Legacy control was reinterpreted as a signal");save_machine(d,file);const auto saved=read(file); check(saved.find("format = 10")!=std::string::npos&&saved.find("'assignments'")!=std::string::npos&&saved.find("'ecu_assignment'")!=std::string::npos,"Current format did not serialize stable port/method keys"); const auto roundtrip=load_machine(file);check(roundtrip.links.size()==d.links.size()&&roundtrip.links[0].a.port==0&&roundtrip.links[0].b.port==2,"Stable key round-trip changed legacy wiring"); auto multiple=d;multiple.links.push_back(multiple.links.front());rejects([&]{validate_machine(multiple);},"Duplicate driver was accepted"); auto reversed=d;std::swap(reversed.links[0].a,reversed.links[0].b);rejects([&]{validate_machine(reversed);},"Noncanonical ECU connection passed document validation"); check(!machine_connection_preview(d,{1,0},{2,0}).allowed,"Preview accepted a gas/control mismatch"); const auto custom=[](bool reorder){return std::string(R"(format = 5 name = 'Stable custom ports' [[parts]] id = 1 kind = 'pipe' position_m = [0,0,0] points_m = [[-0.2,0,0],[0,0,0]] point_diameters_m = [0.02,0.02] [[parts]] id = 2 kind = 'chamber' position_m = [0,0,0] )")+(reorder?"port_coordinates = [[1,0,0],[0,0,0]]\nport_keys = ['tap_b','tap_a']\n":"port_coordinates = [[0,0,0],[1,0,0]]\nport_keys = ['tap_a','tap_b']\n")+R"([[connections]] a_part = 1 a_port = 'end' b_part = 2 b_port = 'tap_a' method = 'orifice' )";}; write(file,custom(false));const auto original=load_machine(file);write(file,custom(true));const auto reordered=load_machine(file); check(original.links.front().b.port==0&&reordered.links.front().b.port==1,"Stable key did not follow a reordered physical port"); check(machine_port_position(*original.find(2),0)==machine_port_position(*reordered.find(2),1),"Reordered port changed its physical endpoint"); auto invalid=custom(false);invalid.replace(invalid.find("'tap_a'",invalid.find("[[connections]]")),7,"'unknown'");write(file,invalid);rejects([&]{load_machine(file);},"Unknown serialized port key silently reconnected"); invalid=custom(false);invalid.replace(invalid.find("'orifice'"),9,"'unknown'");write(file,invalid);rejects([&]{load_machine(file);},"Unknown serialized method was accepted"); auto duplicate=original;duplicate.find(2)->port_keys={"tap_a","tap_a"};rejects([&]{validate_machine(duplicate);},"Duplicate custom identities were accepted"); write(file,R"(format = 4 [[parts]] id = 1 kind = 'pipe' point_diameters_m = [0.04,0.04] [[parts]] id = 2 kind = 'junction' junction_method = 1 position_m = [0.15,0.25,0] [[connections]] a_part = 1 a_port = 1 b_part = 2 b_port = 5 method = 0 )"); const auto alias=load_machine(file);check(alias.links.front().b.port==0&&machine_port_count(*alias.find(2))==1,"Legacy junction alias did not migrate to its shared port"); for(const auto& source:machine_files()){auto machine=load_machine(source);const auto copy=temp.path/source.filename();save_machine(machine,copy);const auto loaded=load_machine(copy);check(loaded.parts.size()==machine.parts.size()&&loaded.links.size()==machine.links.size(),"Existing machine changed topology during migration");} std::cout<<"Typed port domains, quantities, validation and version 5 migration passed\n";return 0; }catch(const std::exception& error){std::cerr<