libMesh
fe_side_test.C
Go to the documentation of this file.
1 #include "fe_test.h" // For linear_test(), headers, FETestBase, etc
2 
3 // Helper functions for restricting normal components
4 namespace {
5 
6 template <typename MyTensor, typename MyVector>
7 MyTensor tangential_hessian (const MyTensor hess,
8  const MyVector normal)
9 {
10  auto full_hess_n = hess*normal;
11  MyTensor tang_hess = hess;
12 
13  const MyTensor n_n = outer_product(normal, normal);
14  for (unsigned int k=0; k != 3; ++k)
15  {
16  MyVector ek {0};
17  ek(k) = 1;
18  const auto Hkn = ek * full_hess_n;
19  const auto Hkn_ek = Hkn*ek;
20  tang_hess -= outer_product(Hkn_ek, normal);
21  tang_hess -= outer_product(normal, Hkn_ek);
22  tang_hess += 2.*(Hkn_ek*normal) * n_n;
23  }
24  tang_hess -= normal*(full_hess_n) * n_n;
25 
26  return tang_hess;
27 }
28 
29 } // anonymous namespace
30 
31 #define SIDEFETEST \
32  CPPUNIT_TEST( testU ); \
33  CPPUNIT_TEST( testGradU ); \
34  CPPUNIT_TEST( testGradUComp ); \
35  CPPUNIT_TEST( testHessU ); \
36  CPPUNIT_TEST( testHessUComp );
37 
38 const unsigned int N_x=2;
39 
40 template <Order order, FEFamily family, ElemType elem_type>
41 class FESideTest : public FETestBase<order, family, elem_type, N_x> {
42 
43 private:
44  std::unique_ptr<FEBase> _fe_side;
45  std::unique_ptr<QGauss> _qrule_side;
46 
47 public:
48  void setUp()
49  {
51 
52  // If we're a 2D mesh on a 3D libMesh build we should be able to
53  // handle inverted elements just fine. Let's test that.
54  BoundaryInfo & bdy_info = this->_mesh->get_boundary_info();
55  for (auto e : this->_mesh->element_ptr_range())
56  if (!(e->id() % 3) && e->dim() < LIBMESH_DIM)
57  e->flip(&bdy_info);
58 
59  FEType fe_type = this->_sys->variable_type(0);
60  _fe_side = FEBase::build(this->_dim, fe_type);
61 
62  _qrule_side = std::make_unique<QGauss>(this->_dim-1, fe_type.default_quadrature_order());
63  _fe_side->attach_quadrature_rule(this->_qrule_side.get());
64 
65  _fe_side->get_xyz();
66  _fe_side->get_normals();
67  _fe_side->get_phi();
68  _fe_side->get_dphi();
69  _fe_side->get_dphidx();
70 #if LIBMESH_DIM > 1
71  _fe_side->get_dphidy();
72 #endif
73 #if LIBMESH_DIM > 2
74  _fe_side->get_dphidz();
75 #endif
76 
77 #if LIBMESH_ENABLE_SECOND_DERIVATIVES
78 
79  // Clough-Tocher elements still don't work multithreaded
80  if (family == CLOUGH && libMesh::n_threads() > 1)
81  return;
82 
83  // Szabab elements don't have second derivatives yet
84  if (family == SZABAB)
85  return;
86 
87  _fe_side->get_d2phi();
88  _fe_side->get_d2phidx2();
89 #if LIBMESH_DIM > 1
90  _fe_side->get_d2phidxdy();
91  _fe_side->get_d2phidy2();
92 #endif
93 #if LIBMESH_DIM > 2
94  _fe_side->get_d2phidxdz();
95  _fe_side->get_d2phidydz();
96  _fe_side->get_d2phidz2();
97 #endif
98 
99 #endif
100  }
101 
102  void tearDown()
103  {
105  }
106 
107  template <typename Functor>
108  void testSideLoop(Functor f)
109  {
110  // Clough-Tocher elements still don't work multithreaded
111  if (family == CLOUGH && libMesh::n_threads() > 1)
112  return;
113 
114  for (auto e : this->_mesh->active_local_element_ptr_range())
115  {
116  this->_elem = e;
117 
118  this->_sys->get_dof_map().dof_indices(this->_elem, this->_dof_indices);
119 
120  for (unsigned int s=0; s != this->_elem->n_sides(); ++s)
121  {
122  _fe_side->reinit(this->_elem, s);
123 
124  f();
125  }
126  }
127  }
128 
129  void testU()
130  {
131  LOG_UNIT_TEST;
132 
133  auto f = [this]() {
134  Parameters dummy;
135 
136  auto phi = this->_fe_side->get_phi();
137  CPPUNIT_ASSERT(phi.size());
138  CPPUNIT_ASSERT_EQUAL(phi.size(), this->_dof_indices.size());
139 
140  std::size_t n_qp = phi[0].size();
141  CPPUNIT_ASSERT(n_qp > 0);
142 
143  auto xyz = this->_fe_side->get_xyz();
144  CPPUNIT_ASSERT_EQUAL(n_qp, xyz.size());
145 
146  for (auto qp : index_range(phi[0]))
147  {
148  Number u = 0;
149  for (auto d : index_range(phi))
150  u += phi[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
151 
152  const Point p = xyz[qp];
153  Real x = p(0),
154  y = LIBMESH_DIM > 1 ? p(1) : 0,
155  z = LIBMESH_DIM > 2 ? p(2) : 0;
156 
157  if (family == RATIONAL_BERNSTEIN && order > 1)
158  LIBMESH_ASSERT_NUMBERS_EQUAL
159  (rational_test(p, dummy, "", ""), u, this->_value_tol);
160  else if (order > 3)
161  LIBMESH_ASSERT_NUMBERS_EQUAL
162  (fe_quartic_test(p, dummy, "", ""), u, this->_value_tol);
163  else if (order > 2)
164  LIBMESH_ASSERT_NUMBERS_EQUAL
165  (fe_cubic_test(p, dummy, "", ""), u, this->_value_tol);
166  else if (order > 1)
167  LIBMESH_ASSERT_NUMBERS_EQUAL
168  (x*x + 0.5*y*y + 0.25*z*z + 0.125*x*y +
169  0.0625*x*z + 0.03125*y*z,
170  u, this->_value_tol);
171  else
172  LIBMESH_ASSERT_NUMBERS_EQUAL (x + 0.25*y + 0.0625*z, u,
173  this->_value_tol);
174  }
175  };
176 
177  testSideLoop(f);
178  }
179 
180  void testGradU()
181  {
182  LOG_UNIT_TEST;
183 
184  auto f = [this]() {
185  Parameters dummy;
186 
187  auto dphi = this->_fe_side->get_dphi();
188  CPPUNIT_ASSERT(dphi.size() > 0);
189  CPPUNIT_ASSERT_EQUAL(dphi.size(), this->_dof_indices.size());
190 
191  std::size_t n_qp = dphi[0].size();
192  CPPUNIT_ASSERT(n_qp > 0);
193 
194  auto xyz = this->_fe_side->get_xyz();
195  CPPUNIT_ASSERT_EQUAL(n_qp, xyz.size());
196 
197  auto normals = this->_fe_side->get_normals();
198  CPPUNIT_ASSERT_EQUAL(n_qp, normals.size());
199 
200  const Point c = this->_elem->vertex_average();
201 
202  for (auto qp : index_range(dphi[0]))
203  {
204  Gradient grad_u = 0;
205  for (auto d : index_range(dphi))
206  grad_u += dphi[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
207 
208  const Point p = xyz[qp];
209  const Point n = normals[qp];
210 
211  // All our normals should be unit normals.
212  LIBMESH_ASSERT_FP_EQUAL(Real(n.norm()),
213  Real(1),
214  this->_grad_tol);
215 
216  // All our normals should be outward-facing. For the simple
217  // meshes here this is sufficient to make sure they're not
218  // flipped:
219  CPPUNIT_ASSERT_GREATER(Real(0), (p-c)*n);
220 
221  // We can only trust the tangential component of gradient to
222  // match! The gradient in the normal direction should be 0
223  // for a shape-preserving master->physical element
224  // transformation, and could be just about anything for a
225  // skew transformation (as occurs when we build meshes with
226  // triangles, tets, prisms, pyramids...)
227 
228  const Gradient tangential_grad_u = grad_u - ((grad_u * n) * n);
229 
230  if (family == RATIONAL_BERNSTEIN && order > 1)
231  {
232  // TODO: Yeah we'll test the ugly expressions later.
233  return;
234  }
235 
236  RealGradient true_grad = this->true_gradient(p);
237  if (order == 0)
238  true_grad.zero();
239 
240  const Gradient true_tangential_grad = true_grad - ((true_grad * n) * n);
241 
242  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(0),
243  true_tangential_grad(0),
244  this->_grad_tol);
245  if (this->_dim > 1)
246  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(1),
247  true_tangential_grad(1),
248  this->_grad_tol);
249  if (this->_dim > 2)
250  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(2),
251  true_tangential_grad(2),
252  this->_grad_tol);
253  }
254  };
255 
256  testSideLoop(f);
257  }
258 
260  {
261  LOG_UNIT_TEST;
262 
263  auto f = [this]() {
264  Parameters dummy;
265 
266  auto dphidx = this->_fe_side->get_dphidx();
267  CPPUNIT_ASSERT(dphidx.size() > 0);
268  CPPUNIT_ASSERT_EQUAL(dphidx.size(), this->_dof_indices.size());
269 #if LIBMESH_DIM > 1
270  auto dphidy = this->_fe_side->get_dphidy();
271  CPPUNIT_ASSERT_EQUAL(dphidy.size(), this->_dof_indices.size());
272 #endif
273 #if LIBMESH_DIM > 2
274  auto dphidz = this->_fe_side->get_dphidz();
275  CPPUNIT_ASSERT_EQUAL(dphidz.size(), this->_dof_indices.size());
276 #endif
277 
278  std::size_t n_qp = dphidx[0].size();
279  CPPUNIT_ASSERT(n_qp > 0);
280 
281  auto xyz = this->_fe_side->get_xyz();
282  CPPUNIT_ASSERT_EQUAL(n_qp, xyz.size());
283 
284  auto normals = this->_fe_side->get_normals();
285  CPPUNIT_ASSERT_EQUAL(n_qp, normals.size());
286 
287  for (auto qp : index_range(dphidx[0]))
288  {
289  Number grad_u_x = 0, grad_u_y = 0, grad_u_z = 0;
290  for (auto d : index_range(dphidx))
291  {
292  grad_u_x += dphidx[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
293 #if LIBMESH_DIM > 1
294  grad_u_y += dphidy[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
295 #endif
296 #if LIBMESH_DIM > 2
297  grad_u_z += dphidz[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
298 #endif
299  }
300 
301  const Point p = xyz[qp];
302 
303  // We can only trust the tangential component of gradient to
304  // match! The gradient in the normal direction should be 0
305  // for a shape-preserving master->physical element
306  // transformation, and could be just about anything for a
307  // skew transformation (as occurs when we build meshes with
308  // triangles, tets, prisms, pyramids...)
309  Point n = normals[qp];
310 
311  const Gradient grad_u {grad_u_x, grad_u_y, grad_u_z};
312  const Gradient tangential_grad_u = grad_u - ((grad_u * n) * n);
313 
314  if (family == RATIONAL_BERNSTEIN && order > 1)
315  {
316  // TODO: Yeah we'll test the ugly expressions later.
317  return;
318  }
319 
320  RealGradient true_grad = this->true_gradient(p);
321  if (order == 0)
322  true_grad.zero();
323 
324  const Gradient true_tangential_grad = true_grad - ((true_grad * n) * n);
325 
326  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(0),
327  true_tangential_grad(0),
328  this->_grad_tol);
329  if (this->_dim > 1)
330  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(1),
331  true_tangential_grad(1),
332  this->_grad_tol);
333  if (this->_dim > 2)
334  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_grad_u(2),
335  true_tangential_grad(2),
336  this->_grad_tol);
337  }
338  };
339 
340  testSideLoop(f);
341  }
342 
343 
344  void testHessU()
345  {
346  LOG_UNIT_TEST;
347 
348 #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES
349  // Szabab elements don't have second derivatives yet
350  if (family == SZABAB)
351  return;
352 
353  auto f = [this]() {
354  Parameters dummy;
355 
356  auto d2phi = this->_fe_side->get_d2phi();
357  CPPUNIT_ASSERT(d2phi.size() > 0);
358  CPPUNIT_ASSERT_EQUAL(d2phi.size(), this->_dof_indices.size());
359 
360  std::size_t n_qp = d2phi[0].size();
361  CPPUNIT_ASSERT(n_qp > 0);
362 
363  auto xyz = this->_fe_side->get_xyz();
364  CPPUNIT_ASSERT_EQUAL(n_qp, xyz.size());
365 
366  auto normals = this->_fe_side->get_normals();
367  CPPUNIT_ASSERT_EQUAL(n_qp, normals.size());
368 
369  for (auto qp : index_range(d2phi[0]))
370  {
371  Tensor hess_u;
372  for (auto d : index_range(d2phi))
373  hess_u += d2phi[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
374 
375  // We can only trust the tangential-tangential components of
376  // hessians, t1'*H*t2 for tangential vectors t1 and t2, to
377  // match! The hessian components in the normal direction
378  // should be 0 for a shape-preserving master->physical
379  // element transformation, but could be just about anything
380  // for a skew transformation (as occurs when we build meshes
381  // with triangles, tets, prisms, pyramids...)
382  Point n = normals[qp];
383  hess_u = tangential_hessian(hess_u, n);
384 
385  if (family == RATIONAL_BERNSTEIN && order > 1)
386  {
387  // TODO: Yeah we'll test the ugly expressions later.
388  return;
389  }
390 
391  const Point p = xyz[qp];
392  RealTensor true_hess = this->true_hessian(p);
393 
394  // Subtract off values only relevant in normal directions
395  RealTensor tangential_hess = tangential_hessian(true_hess, n);
396 
397  LIBMESH_ASSERT_NUMBERS_EQUAL(tangential_hess(0,0),
398  hess_u(0,0), this->_hess_tol);
399  if (this->_dim > 1)
400  {
401  LIBMESH_ASSERT_NUMBERS_EQUAL
402  (hess_u(0,1), hess_u(1,0), this->_hess_tol);
403  LIBMESH_ASSERT_NUMBERS_EQUAL
404  (tangential_hess(0,1), hess_u(0,1), this->_hess_tol);
405  LIBMESH_ASSERT_NUMBERS_EQUAL
406  (tangential_hess(1,1), hess_u(1,1), this->_hess_tol);
407  }
408  if (this->_dim > 2)
409  {
410  LIBMESH_ASSERT_NUMBERS_EQUAL
411  (hess_u(0,2), hess_u(2,0), this->_hess_tol);
412  LIBMESH_ASSERT_NUMBERS_EQUAL
413  (hess_u(1,2), hess_u(2,1), this->_hess_tol);
414  LIBMESH_ASSERT_NUMBERS_EQUAL
415  (tangential_hess(0,2), hess_u(0,2), this->_hess_tol);
416  LIBMESH_ASSERT_NUMBERS_EQUAL
417  (tangential_hess(1,2), hess_u(1,2), this->_hess_tol);
418  LIBMESH_ASSERT_NUMBERS_EQUAL
419  (tangential_hess(2,2), hess_u(2,2), this->_hess_tol);
420  }
421  }
422  };
423 
424  testSideLoop(f);
425 #endif // LIBMESH_ENABLE_SECOND_DERIVATIVES
426  }
427 
429  {
430  LOG_UNIT_TEST;
431 
432 #ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES
433  // Szabab elements don't have second derivatives yet
434  if (family == SZABAB)
435  return;
436 
437  auto f = [this]() {
438  Parameters dummy;
439 
440  auto d2phidx2 = this->_fe_side->get_d2phidx2();
441  CPPUNIT_ASSERT(d2phidx2.size() > 0);
442  CPPUNIT_ASSERT_EQUAL(d2phidx2.size(), this->_dof_indices.size());
443 #if LIBMESH_DIM > 1
444  auto d2phidxdy = this->_fe_side->get_d2phidxdy();
445  CPPUNIT_ASSERT_EQUAL(d2phidxdy.size(), this->_dof_indices.size());
446  auto d2phidy2 = this->_fe_side->get_d2phidy2();
447  CPPUNIT_ASSERT_EQUAL(d2phidy2.size(), this->_dof_indices.size());
448 #endif
449 #if LIBMESH_DIM > 2
450  auto d2phidxdz = this->_fe_side->get_d2phidxdz();
451  CPPUNIT_ASSERT_EQUAL(d2phidxdz.size(), this->_dof_indices.size());
452  auto d2phidydz = this->_fe_side->get_d2phidydz();
453  CPPUNIT_ASSERT_EQUAL(d2phidydz.size(), this->_dof_indices.size());
454  auto d2phidz2 = this->_fe_side->get_d2phidz2();
455  CPPUNIT_ASSERT_EQUAL(d2phidz2.size(), this->_dof_indices.size());
456 #endif
457 
458  std::size_t n_qp = d2phidx2[0].size();
459  CPPUNIT_ASSERT(n_qp > 0);
460 
461  auto xyz = this->_fe_side->get_xyz();
462  CPPUNIT_ASSERT_EQUAL(n_qp, xyz.size());
463 
464  auto normals = this->_fe_side->get_normals();
465  CPPUNIT_ASSERT_EQUAL(n_qp, normals.size());
466 
467  for (auto qp : index_range(d2phidx2[0]))
468  {
469  Tensor hess_u;
470  for (auto d : index_range(d2phidx2))
471  {
472  hess_u(0,0) += d2phidx2[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
473 #if LIBMESH_DIM > 1
474  hess_u(0,1) += d2phidxdy[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
475  hess_u(1,1) += d2phidy2[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
476 #endif
477 #if LIBMESH_DIM > 2
478  hess_u(0,2) += d2phidxdz[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
479  hess_u(1,2) += d2phidydz[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
480  hess_u(2,2) += d2phidz2[d][qp] * (*this->_sys->current_local_solution)(this->_dof_indices[d]);
481 #endif
482  }
483 
484 #if LIBMESH_DIM > 1
485  hess_u(1,0) = hess_u(0,1);
486 #endif
487 #if LIBMESH_DIM > 2
488  hess_u(2,0) = hess_u(0,2);
489  hess_u(2,1) = hess_u(1,2);
490 #endif
491 
492  // We can only trust the tangential-tangential components of
493  // hessians, t1'*H*t2 for tangential vectors t1 and t2, to
494  // match! The hessian components in the normal direction
495  // should be 0 for a shape-preserving master->physical
496  // element transformation, but could be just about anything
497  // for a skew transformation (as occurs when we build meshes
498  // with triangles, tets, prisms, pyramids...)
499  Point n = normals[qp];
500  hess_u = tangential_hessian(hess_u, n);
501 
502  if (family == RATIONAL_BERNSTEIN && order > 1)
503  return;
504 
505  const Point p = xyz[qp];
506  RealTensor true_hess = this->true_hessian(p);
507 
508  // Subtract off values only relevant in normal directions
509  RealTensor tangential_hess = tangential_hessian(true_hess, n);
510 
511  LIBMESH_ASSERT_NUMBERS_EQUAL
512  (tangential_hess(0,0), hess_u(0,0), this->_hess_tol);
513  if (this->_dim > 1)
514  {
515  LIBMESH_ASSERT_NUMBERS_EQUAL
516  (tangential_hess(0,1), hess_u(0,1), this->_hess_tol);
517  LIBMESH_ASSERT_NUMBERS_EQUAL
518  (tangential_hess(1,1), hess_u(1,1), this->_hess_tol);
519  }
520  if (this->_dim > 2)
521  {
522  LIBMESH_ASSERT_NUMBERS_EQUAL
523  (tangential_hess(0,2), hess_u(0,2), this->_hess_tol);
524  LIBMESH_ASSERT_NUMBERS_EQUAL
525  (tangential_hess(1,2), hess_u(1,2), this->_hess_tol);
526  LIBMESH_ASSERT_NUMBERS_EQUAL
527  (tangential_hess(2,2), hess_u(2,2), this->_hess_tol);
528  }
529  }
530  };
531 
532  testSideLoop(f);
533 #endif // LIBMESH_ENABLE_SECOND_DERIVATIVES
534  }
535 
536 };
537 
538 
539 #define INSTANTIATE_FESIDETEST(order, family, elemtype) \
540  class FESideTest_##order##_##family##_##elemtype : public FESideTest<order, family, elemtype> { \
541  public: \
542  FESideTest_##order##_##family##_##elemtype() : \
543  FESideTest<order,family,elemtype>() { \
544  if (unitlog->summarized_logs_enabled()) \
545  this->libmesh_suite_name = "FESideTest"; \
546  else \
547  this->libmesh_suite_name = "FESideTest_" #order "_" #family "_" #elemtype; \
548  } \
549  CPPUNIT_TEST_SUITE( FESideTest_##order##_##family##_##elemtype ); \
550  SIDEFETEST \
551  CPPUNIT_TEST_SUITE_END(); \
552  }; \
553  \
554  CPPUNIT_TEST_SUITE_REGISTRATION( FESideTest_##order##_##family##_##elemtype );
555 
556 
562 
563 #if LIBMESH_DIM > 1
569 
575 
581 
587 #endif
588 
589 #if LIBMESH_DIM > 2
595 //
601 //
607 //
613 #endif
void setUp()
Definition: fe_side_test.C:48
INSTANTIATE_FESIDETEST(CONSTANT, SIDE_HIERARCHIC, EDGE3)
RealVectorValue RealGradient
std::unique_ptr< FEBase > _fe_side
Definition: fe_side_test.C:44
TypeTensor< typename CompareTypes< T, T2 >::supertype > outer_product(const TypeVector< T > &a, const TypeVector< T2 > &b)
Definition: type_tensor.h:1433
unsigned int n_threads()
Definition: libmesh_base.h:96
std::unique_ptr< QGauss > _qrule_side
Definition: fe_side_test.C:45
RealTensorValue RealTensor
Number fe_cubic_test(const Point &p, const Parameters &, const std::string &, const std::string &)
Definition: fe_test.h:131
void testU()
Definition: fe_side_test.C:129
void tearDown()
Definition: fe_test.h:597
Number rational_test(const Point &p, const Parameters &, const std::string &, const std::string &)
Definition: fe_test.h:203
static RealTensor true_hessian(Point p)
Definition: fe_test.h:321
void testGradU()
Definition: fe_side_test.C:180
const unsigned int N_x
Definition: fe_side_test.C:38
NumberVectorValue Gradient
void testSideLoop(Functor f)
Definition: fe_side_test.C:108
void testHessU()
Definition: fe_side_test.C:344
void tearDown()
Definition: fe_side_test.C:102
void testHessUComp()
Definition: fe_side_test.C:428
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< dof_id_type > _dof_indices
Definition: fe_test.h:273
NumberTensorValue Tensor
static RealGradient true_gradient(Point p)
Definition: fe_test.h:283
void testGradUComp()
Definition: fe_side_test.C:259
Number fe_quartic_test(const Point &p, const Parameters &, const std::string &, const std::string &)
Definition: fe_test.h:164
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:117
void setUp()
Definition: fe_test.h:350