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