KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_test_utils.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 * @author Jon Evans <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21
22#ifndef KICAD_API_TEST_UTILS_H
23#define KICAD_API_TEST_UTILS_H
24
25#include <boost/test/unit_test.hpp>
26#include <boost/bimap.hpp>
27#include <google/protobuf/any.pb.h>
28#include <google/protobuf/util/message_differencer.h>
29#include <memory>
30#include <set>
31#include <magic_enum.hpp>
32#include <type_traits>
33#include <unordered_set>
34
35#include <api/api_enums.h>
36#include <eda_group.h>
37#include <eda_item.h>
39
47template <typename KiCadEnum, typename ProtoEnum>
48void testEnums( bool aPartiallyMapped = false, std::optional<KiCadEnum> aNullMapping = std::nullopt )
49{
50 boost::bimap<ProtoEnum, KiCadEnum> protoToKiCadSeen;
51 std::set<ProtoEnum> seenProtos;
52
53 for( ProtoEnum value : magic_enum::enum_values<ProtoEnum>() )
54 {
55 BOOST_TEST_CONTEXT( magic_enum::enum_type_name<ProtoEnum>() << "::" << magic_enum::enum_name( value ) )
56 {
57 std::string name( magic_enum::enum_name( value ) );
58 auto splitPos = name.find_first_of( '_' );
59
60 // Protobuf enum names should be formatted as PREFIX_KEY
61 BOOST_REQUIRE_MESSAGE( splitPos != std::string::npos, "Proto enum name doesn't have a prefix" );
62
63 std::string suffix = name.substr( splitPos );
64
65 // Protobuf enum with the value 0 should not map to anything
66 if( static_cast<int>( value ) == 0 )
67 {
68 BOOST_REQUIRE_MESSAGE( suffix.compare( "_UNKNOWN" ) == 0,
69 "Proto enum with value 0 must be named <PREFIX>_UNKNOWN" );
70 continue;
71 }
72
73 KiCadEnum result;
74 // Every non-unknown Proto value should map to a valid KiCad value
75 BOOST_REQUIRE_NO_THROW( result = ( FromProtoEnum<KiCadEnum, ProtoEnum>( value ) ) );
76
77 // There should be a 1:1 mapping
78 BOOST_REQUIRE( !protoToKiCadSeen.left.count( value ) );
79 protoToKiCadSeen.left.insert( { value, result } );
80 }
81 }
82
83 for( KiCadEnum value : magic_enum::enum_values<KiCadEnum>() )
84 {
85 BOOST_TEST_CONTEXT( magic_enum::enum_type_name<KiCadEnum>() << "::" << magic_enum::enum_name( value ) )
86 {
87 ProtoEnum result;
88
89 if( aPartiallyMapped )
90 {
91 try
92 {
94 }
96 {
97 // If it wasn't mapped from KiCad to Proto, it shouldn't be mapped the other way
98 BOOST_REQUIRE_MESSAGE( !protoToKiCadSeen.right.count( value ),
99 "Proto enum is mapped to this KiCad enum, but not vice versa" );
100 continue;
101 }
102 }
103 else
104 {
105 // Every KiCad enum value should map to a non-unknown Protobuf value
106 BOOST_REQUIRE_NO_THROW( result = ( ToProtoEnum<KiCadEnum, ProtoEnum>( value ) ) );
107 }
108
109 // Protobuf "unknown" should always be zero value by convention
110 BOOST_REQUIRE( ( result != static_cast<ProtoEnum>( 0 ) )
111 || ( aNullMapping && *aNullMapping == value ) );
112
113 // There should be a 1:1 mapping
114 BOOST_REQUIRE( !seenProtos.count( result ) );
115 seenProtos.insert( result );
116
117 // Round-tripping should work
119 BOOST_REQUIRE( roundTrip == value );
120 }
121 }
122}
123
124
125template<typename ProtoClass, typename KiCadClass, typename Factory>
126void testProtoFromKiCadObject( KiCadClass* aInput, Factory&& aCreateOutput )
127{
128 BOOST_TEST_CONTEXT( aInput->GetFriendlyName() << ": " << aInput->m_Uuid.AsStdString() )
129 {
130 constexpr bool isGroup = std::is_base_of_v<EDA_GROUP, std::decay_t<KiCadClass>>;
131
132 std::unordered_set<EDA_ITEM*> cachedItems;
133
134 if constexpr( isGroup )
135 cachedItems = aInput->GetItems();
136
137 google::protobuf::Any any;
138 BOOST_REQUIRE_NO_THROW( aInput->Serialize( any ) );
139
140 ProtoClass proto;
141 BOOST_REQUIRE_MESSAGE( any.UnpackTo( &proto ),
142 "Any message did not unpack into the requested type" );
143
144 std::unique_ptr<KiCadClass> output = aCreateOutput();
145
146 bool deserializeResult = false;
147 BOOST_REQUIRE_NO_THROW( deserializeResult = output->Deserialize( any ) );
148 BOOST_REQUIRE_MESSAGE( deserializeResult, "Deserialize failed" );
149
150 if constexpr( isGroup )
151 output->FinalizeGroupDeserialization();
152
153 google::protobuf::Any outputAny;
154 BOOST_REQUIRE_NO_THROW( output->Serialize( outputAny ) );
155
156 ProtoClass outputProto;
157 BOOST_REQUIRE_MESSAGE( outputAny.UnpackTo( &outputProto ),
158 "Round-tripped Any message did not unpack into the requested type" );
159
160 if( !google::protobuf::util::MessageDifferencer::Equals( proto, outputProto ) )
161 {
162 BOOST_TEST_MESSAGE( "Input: " << proto.Utf8DebugString() );
163 BOOST_TEST_MESSAGE( "Output: " << outputProto.Utf8DebugString() );
164 BOOST_TEST_FAIL( "Round-tripped protobuf does not match" );
165 }
166
167 if constexpr( isGroup )
168 {
169 if( output->GetItems() != cachedItems )
170 BOOST_TEST_FAIL( "Round-tripped group does not match" );
171
172 // Restore cached items into original group
173 for( EDA_ITEM* item : cachedItems )
174 aInput->AddItem( item );
175 }
176 else if( !output->operator==( *aInput ) )
177 {
178 BOOST_TEST_FAIL( "Round-tripped object does not match" );
179 }
180 }
181}
182
183
184template<typename ProtoClass, typename KiCadClass, typename ParentClass>
185void testProtoFromKiCadObject( KiCadClass* aInput, ParentClass* aParent, bool aStrict = true )
186{
187 if( aStrict )
188 {
190 [aParent]() { return std::make_unique<KiCadClass>( aParent ); } );
191 }
192 else
193 {
195 [aInput]()
196 {
197 std::unique_ptr<KiCadClass> cloned( static_cast<KiCadClass*>( aInput->Clone() ) );
198 return std::make_unique<KiCadClass>( *cloned );
199 } );
200 }
201}
202
203#endif //KICAD_API_TEST_UTILS_H
const char * name
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
void testEnums(bool aPartiallyMapped=false, std::optional< KiCadEnum > aNullMapping=std::nullopt)
Checks if a KiCad enum has been properly mapped to a Protobuf enum.
void testProtoFromKiCadObject(KiCadClass *aInput, Factory &&aCreateOutput)
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
An exception class to represent a WX assertion.
Definition wx_assert.h:44
A type-safe container of any type.
Definition ki_any.h:92
static void roundTrip(const DIFF_VALUE &aValue)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.