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