1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 Test cases in
11 See: test.component.generator
12 */
13 module cpptooling.generator.gtest;
14 
15 import std.range : isInputRange;
16 
17 import dsrcgen.cpp : CppModule, E;
18 
19 import dextool.type : Path, DextoolVersion;
20 
21 import cpptooling.data : CppClass, FullyQualifiedNameType, TypeKindVariable;
22 import cpptooling.data.symbol : Container;
23 import cpptooling.type : CustomHeader;
24 
25 // argument names used in the generated code
26 private immutable argValue = "x0";
27 private immutable argStream = "os";
28 
29 auto generateGtestHdr(Path if_file, Path incl_guard, DextoolVersion ver,
30         CustomHeader custom_hdr, CppModule gtest) {
31     import std.path : baseName;
32     import dsrcgen.cpp : CppHModule;
33     import cpptooling.generator.includes : convToIncludeGuard, makeHeader;
34 
35     auto o = CppHModule(convToIncludeGuard(incl_guard));
36     o.header.append(makeHeader(incl_guard, ver, custom_hdr));
37     o.content.include(if_file.baseName);
38     o.content.include("gtest/gtest.h");
39     o.content.sep(2);
40     o.content.append(gtest);
41 
42     return o;
43 }
44 
45 /** Generate a compare operator for use with EXPECT_EQ in gtest.
46  *
47  * Optimized compares using == for primitive types except floats.
48  * Google tests internal helper for all others.
49  */
50 void generateGtestPrettyEqual(T)(T members, const FullyQualifiedNameType name,
51         string guard_prefix, ref const Container container, CppModule m) {
52     import std.algorithm : map, among;
53     import std.ascii : isAlphaNum;
54     import std.conv : to;
55     import std.format : format;
56     import std.string : toUpper;
57     import logger = std.experimental.logger;
58 
59     import cpptooling.data.kind : resolveTypeRef;
60     import cpptooling.data : TypeKind, USRType, TypeAttr, isIncompleteArray;
61 
62     auto findType(USRType a) {
63         return container.find!TypeKind(a);
64     }
65 
66     static void fieldCompare(string field, TypeKind canonical_t, CppModule code) {
67         if (canonical_t.info.kind == TypeKind.Info.Kind.primitive) {
68             auto info = cast(TypeKind.PrimitiveInfo) canonical_t.info;
69             // reuse google tests internal helper for floating points because it does an ULP*4
70             if (info.fmt.typeId.among("float", "double", "long double")) {
71                 // long double do not work with the template thus reducing to a double
72                 code.stmt(format(
73                         `acc = acc && ::testing::internal::CmpHelperFloatingPointEQ<%s>("", "", lhs.%s, rhs.%s)`,
74                         info.fmt.typeId == "long double" ? "double" : info.fmt.typeId, field, field));
75             } else {
76                 code.stmt(E("acc") = E("acc && " ~ format("lhs.%s == rhs.%s", field, field)));
77             }
78         } else {
79             code.stmt(format(`acc = acc && ::testing::internal::CmpHelperEQ("", "", lhs.%s, rhs.%s)`,
80                     field, field));
81         }
82     }
83 
84     auto ifndef = m.IFNDEF(format("%s_NO_CMP_%s", guard_prefix.toUpper,
85             name.map!(a => a.isAlphaNum ? a : '_')
86             .map!(a => a.to!char)));
87 
88     auto func = ifndef.func_body("inline bool", "operator==",
89             format("const %s& lhs, const %s& rhs", name, name));
90 
91     func.stmt("bool acc = true");
92 
93     foreach (mem; members) {
94         TypeKind kind = mem.type.kind;
95         auto canonical_t = resolveTypeRef(kind, &findType);
96 
97         // a constant array compares element vise. For now can only handle one dimensional arrays
98         if (canonical_t.info.kind == TypeKind.Info.Kind.array
99                 && !isIncompleteArray(canonical_t.info.indexes)
100                 && canonical_t.info.indexes.length == 1) {
101             auto elem_t = findType(canonical_t.info.element).front;
102             auto canonical_elem_t = resolveTypeRef(elem_t, &findType);
103             auto loop = func.for_("unsigned int dextool_i = 0",
104                     "dextool_i < " ~ canonical_t.info.indexes[0].to!string, "++dextool_i");
105             fieldCompare(mem.name ~ "[dextool_i]", canonical_elem_t, loop);
106             with (loop.if_("!acc"))
107                 return_("false");
108         } else {
109             fieldCompare(mem.name, canonical_t, func);
110         }
111     }
112 
113     func.return_("acc");
114     m.sep(2);
115 }
116 
117 /** Generate Google Test pretty printers of a PODs public members.
118  *
119  * Params:
120  *  src = POD to generate the pretty printer for.
121  *  m = module to generate code in.
122  */
123 void generateGtestPrettyPrintHdr(const FullyQualifiedNameType name, CppModule m) {
124     import std.format : format;
125 
126     m.func("void", "PrintTo", format("const %s& %s, ::std::ostream* %s", name,
127             argValue, argStream));
128     m.sep(2);
129 }
130 
131 /** Generate Google Test pretty printers of a PODs public members.
132  *
133  * This mean that the actual values are printed instead of the byte
134  * representation.
135  *
136  * Params:
137  *  members = range of the members to pretty print
138  *  name = fqn name of the type that have the members
139  *  m = module to generate code in.
140  */
141 void generateGtestPrettyPrintImpl(T)(T members, const FullyQualifiedNameType name, CppModule m)
142         if (isInputRange!T) {
143     import std.algorithm;
144     import std.format : format;
145 
146     auto func = m.func_body("void", "PrintTo",
147             format("const %s& %s, ::std::ostream* %s", name, argValue, argStream));
148 
149     string space = null;
150     foreach (mem; members) {
151         func.stmt(E("*os <<") ~ E(format(`"%s%s:"`, space,
152                 mem.name)) ~ E("<<") ~ E("::testing::PrintToString")(E(argValue).E(mem.name)));
153         space = " ";
154     }
155 
156     m.sep(2);
157 }