יום חמישי, אוגוסט 20, 2015

בדיקות דרך קומפילציה

לא מזמן יצא לי לדבר על בדיקות עם ידידי משכבר הימים ויצא לי להציג שיטות תאומות שאני אוהב לדחוף -

היום הרבה מידידי משתמשים בכלים מצויינים כמו ג'נקינס ו TFS (יש הרבה אחרים אבל הרעיון דומה), מאוד בגדול מדובר על מערכות שדוגמת גרסה מסויימת מריצה מספר בדיקות ונותנת את תוצאת הבדיקות (אצלי המצב טיפה שונה כי יש פתרון in-house) ומדווחת למתכנתים .

השיטה הקלאסית שאני מכיר מדברת על מודל בדיקות שמופעל חיצונית למודל הפנימי שלך או מופעלים בזמן ריצה (מקרה קלאסי של gtest):


#include <iostream>
#include <gtest/gtest.h>

struct foo
{
  int random(){return 4;};//chosen by fair dice roll.
                          //guarnteed to be random :P
                          //by xkcd 221

};



TEST(foo, random) {
  foo f;
  EXPECT_EQ(0, f.random());
}



int main (int argc, char **argv)

{
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

שיטה נוספת מדברת על בנייה ספציפית לקומפיילר בה מקפלים מקרה בודד לבד כל פעם (מה שעושים עם lit ו QTest הרבה מאוד פעמים) , אמנם זה פתרון טוב הרבה מאוד פעמים זה לא עונה על הצרכים שלי (לא להחזיק מקטעי קוד נפרדים).

הבעייה בגישה של בדיקה בזמן ריצה (אפילו בתחילת ריצה) או בזמן בנייה כטסריט חיצוני  היא שזה עדיין משאיר פתח עצום למתכנת לעשות קומיט לדחוף אותו לרפוסטורי כשהוא לא נבנה (אני באמת רוצה לראות את איש ה IT שיצליח לדחוף הוק שמבצע בנייה על מכונה של מפתח שתמנע ממנו לעשות push אם בנייה נכשלת).

עכשיו בגלל שאני חי בעולם יחסית טוב (בכל זאת כותב בשפות מקומפלת) יש לי אפשרות נוספת שהתרגלתי להשתמש בה - כתיבת בדיקות שמכשילות קומפילציה שזה בשונה מבניית פרוייקט כן מתקבלת כקרס (גם ב git ו גם svn) בצד מפתח.

אני יודע זה נשמע עקום מאוד בהתחלה אבל ברגע שמתרגלים לזה זה נהיה מאוד נוח, דבר ראשון זה לא דורש שום סיפריה חיצונית (וחברי שנתקלו בבירוקרטיה לינקוקית יודעים על מה אני מדבר) ודבר שני זה מרגיש מאוד טבעי בקוד.

דוגמה קלאסית למטה דטה של מבנים (שתכשיל קומפילציה ) :



#include <iostream>
#include <cstddef>
#include <type_traits>

struct mytype_t
{
   //these are just example for some fields 
   int field1;
   int field2;

//   int non_declared_field_in_cmo_field_table_name;//this field had not been declared in cmo_field_table_name

   char y[3];
};

struct metadata_t
{
   size_t size;       
   size_t offset;     
   size_t allignment; 
   const char *  name;

} ;

//we have macro that fill metadata_t
//

static constexpr const struct metadata_t cmo_field_table_name[] = 
{
  {sizeof(mytype_t::field1),offsetof(mytype_t,field1) , std::alignment_of < decltype (mytype_t::field1 )> :: value,"1"},
  {sizeof(mytype_t::field2),offsetof(mytype_t,field2) , std::alignment_of < decltype (mytype_t::field2 )> :: value,"bla"},
  {sizeof(mytype_t::y),offsetof(mytype_t,y), std::alignment_of <decltype( mytype_t::y) > :: value,"2"},
};



//We declare a type that will fail compilation in case
template <bool>

struct check_struct_for_missing_field
{

};

template <>

struct check_struct_for_missing_field<true>
{
 //I named this field as field and not anything else so the compiler error will be more understandble 
 enum {field = 0};
};


//the magic start here 
//a recursive defintion that check on compile time if all the previous fields had been declared correctly
//in case of non declared fields this will fail compilation and will give us an error that will prevent a person commiting errosly like this mornign
//
//compile_type_check.cc: In instantiation of ‘struct check_if_all_fields_for_struct_correctly_declared_argument_is_field_number<((const metadata_t*)(& cmo_field_table_name)), 2>’:
//compile_type_check.cc:74:178:   required from here
//compile_type_check.cc:62:9: error: ‘field’ is not a member of ‘check_struct_for_missing_field<false>’
//    enum {check =  check_struct_for_missing_field < check_if_all_fields_for_struct_correctly_declared_argument_is_field_number < ptr,index_in_ptr - 1> :: value == ptr[index_in_ptr].offset  > ::field};
//             ^
//             compile_type_check.cc:74:1: error: static assertion failed: missing fields in tab
//              static_assert(check_if_all_fields_for_struct_correctly_declared_argument_is_field_number <cmo_field_table_name, sizeof(cmo_field_table_name)/sizeof(cmo_field_table_name[0]) -1 >::value == sizeof(mytype_t) , "missing fields in tab");
//
//
template < const struct metadata_t * ptr, int index_in_ptr>
struct check_if_all_fields_for_struct_correctly_declared_argument_is_field_number
{
  //incorrect declartion (type mismatchs) are found here
  //a correct offset of field N is  (alligment + size + offset ) for field N - 1
  enum {value =
        (
          ( ptr[index_in_ptr].allignment != ptr[index_in_ptr].size)?

            ptr[index_in_ptr].size + ptr[index_in_ptr].allignment
            :
            ptr[index_in_ptr].size)
            //this will iterate for the previous fields until the first declared in ptr
          + check_if_all_fields_for_struct_correctly_declared_argument_is_field_number<ptr,index_in_ptr-1>::value};
   //in case we have a missing field the next will fail compilation saying : 
   //
   enum {check =  check_struct_for_missing_field < check_if_all_fields_for_struct_correctly_declared_argument_is_field_number < ptr,index_in_ptr - 1> :: value == ptr[index_in_ptr].offset  > ::field};



};

//this is the final step of fields (the zero in the index

template <const struct metadata_t * ptr>

struct check_if_all_fields_for_struct_correctly_declared_argument_is_field_number <ptr,0>
{
 public:
   enum {value = //cmo_field_table_name[0].offset + 
        ( ptr[0].allignment != ptr[0].size)? ptr[0].size +   ptr[0].allignment :ptr[0].size
       };
};


static_assert(check_if_all_fields_for_struct_correctly_declared_argument_is_field_number <cmo_field_table_name, sizeof(cmo_field_table_name)/sizeof(cmo_field_table_name[0]) -1 >::value == sizeof(mytype_t) , "missing fields in tab");



int main(int argc, char *argv[])
{
  for (auto & r: cmo_field_table_name)
  {

    std::cout << "name:" << r.name<< " offset:" << r.offset << " allignment: " << r.allignment << " size:" << r.size << "total: " << r.size + r.allignment << std::endl;

  };

  return 0;

};

בחרתי ב C++  כי לדעתי היא פשוטה מספיק להבנה אבל דבר מאוד דומה ניתן לבצע גם בשפות אחרות.

JAVA: כן אני זוכר את הנושא של המרה לobject ובעיית הstatic הנקודה היא שימוש בטיפוסי הנתונים כמשתנים.

C: יחי המקרו ! יאפ בנייה על בסיס מקרו.

FPC: אם משהו עדיין משתמש בזה - "

FPC 2.4.0 is neither complete nor practically usable yet. Until that is so, templates can be used." אבל אני די בטוח שיש פתרון אחר.

הכשלת קימפול בגלל בדיקות מאפשרת יכולת נבוט טובות יותר ואני מאמין עם ערך wtf/minute נמוך יחסית.

אין תגובות:

הוסף רשומת תגובה