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 : '_').map!(a => a.to!char)));
85 
86     auto func = ifndef.func_body("inline bool", "operator==",
87             format("const %s& lhs, const %s& rhs", name, name));
88 
89     func.stmt("bool acc = true");
90 
91     foreach (mem; members) {
92         TypeKind kind = mem.type.kind;
93         auto canonical_t = resolveTypeRef(kind, &findType);
94 
95         // a constant array compares element vise. For now can only handle one dimensional arrays
96         if (canonical_t.info.kind == TypeKind.Info.Kind.array
97                 && !isIncompleteArray(canonical_t.info.indexes)
98                 && canonical_t.info.indexes.length == 1) {
99             auto elem_t = findType(canonical_t.info.element).front;
100             auto canonical_elem_t = resolveTypeRef(elem_t, &findType);
101             auto loop = func.for_("unsigned int dextool_i = 0",
102                     "dextool_i < " ~ canonical_t.info.indexes[0].to!string, "++dextool_i");
103             fieldCompare(mem.name ~ "[dextool_i]", canonical_elem_t, loop);
104             with (loop.if_("!acc"))
105                 return_("false");
106         } else {
107             fieldCompare(mem.name, canonical_t, func);
108         }
109     }
110 
111     func.return_("acc");
112     m.sep(2);
113 }
114 
115 /** Generate Google Test pretty printers of a PODs public members.
116  *
117  * Params:
118  *  src = POD to generate the pretty printer for.
119  *  m = module to generate code in.
120  */
121 void generateGtestPrettyPrintHdr(const FullyQualifiedNameType name, CppModule m) {
122     import std.format : format;
123 
124     m.func("void", "PrintTo", format("const %s& %s, ::std::ostream* %s", name,
125             argValue, argStream));
126     m.sep(2);
127 }
128 
129 /** Generate Google Test pretty printers of a PODs public members.
130  *
131  * This mean that the actual values are printed instead of the byte
132  * representation.
133  *
134  * Params:
135  *  members = range of the members to pretty print
136  *  name = fqn name of the type that have the members
137  *  m = module to generate code in.
138  */
139 void generateGtestPrettyPrintImpl(T)(T members, const FullyQualifiedNameType name, CppModule m)
140         if (isInputRange!T) {
141     import std.algorithm;
142     import std.format : format;
143 
144     auto func = m.func_body("void", "PrintTo",
145             format("const %s& %s, ::std::ostream* %s", name, argValue, argStream));
146 
147     string space = null;
148     foreach (mem; members) {
149         func.stmt(E("*os <<") ~ E(format(`"%s%s:"`, space,
150                 mem.name)) ~ E("<<") ~ E("::testing::PrintToString")(E(argValue).E(mem.name)));
151         space = " ";
152     }
153 
154     m.sep(2);
155 }