From 47e70ee935838b3e11f13c4448d7425b80d46357 Mon Sep 17 00:00:00 2001 From: "Shine.Wang" Date: Mon, 21 Oct 2024 16:03:06 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design_docs/dev_design.md | 6 ++++++ docs/pictures/test-flow.png | Bin 30257 -> 41638 bytes docs/pictures/test_module.png | Bin 0 -> 10499 bytes 3 files changed, 6 insertions(+) create mode 100644 docs/pictures/test_module.png diff --git a/docs/design_docs/dev_design.md b/docs/design_docs/dev_design.md index 57c7456..92db96d 100644 --- a/docs/design_docs/dev_design.md +++ b/docs/design_docs/dev_design.md @@ -110,6 +110,8 @@ Mulan V2 ### 3.1 框架概览 +#### 3.1.1 框架目录 + ``` . ├── hwcompatible 框架主功能 @@ -148,6 +150,10 @@ Mulan V2 ├── tests 测试套 └──vendor_tests 厂商测试工具存放目录 ``` +#### 3.1.2 功能模块分级 + +![test_module](../pictures/test_module.png) + ### 3.2 框架特点 diff --git a/docs/pictures/test-flow.png b/docs/pictures/test-flow.png index e2ac041e6d4ed83892fceedb532fb73252279a46..40a2c83d415329bf2ac3141f33f7603c218a31b2 100644 GIT binary patch literal 41638 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 literal 30257 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 NS5EbIu?*Dr-vBB$ZixT@ diff --git a/docs/pictures/test_module.png b/docs/pictures/test_module.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3a232dc1ec8ed4a3a918943b9b4d0c82e4fcff GIT binary patch literal 10499 zcmcJVXIN9+yXJ!;SSTtA(iId$s?wy3^o}B(&^rkr5UL3XuOJ{zq*tX0p-49b2#5%w zLnM??1wsj-g&xYpb7uZCXJ$T3xz2~|z1P0-VU=gE=l#&baB0LLn&LG7Zfrwjm8CtN$RyF_VU@zyl=1ppX2 z{{E=CyuLaD01wl(RFz)^S`(-BT-hfwx3>Acq|tolgK=p*wwrb7)3J8x%6HhM!QCzS z)^1?2w|6${w`Kxwd}smv1lG;Xgk?#!KDCdeB5gQ-&Y_P^#SciKd9F+!Dh$e$Xd4$j zD!rcqLhqeRjwY>!c@stU0*HljDFol=6>$Q1Z?>MFg&b`lH%}Uo(h@6}uqw=v=+Uy$`B(x~ z*b(V)+u>-Rm~~DAAgvmN9hXs7TYy+!i9z1_yI0)$c?u!; zt_y8%GNRIBB02ukW`a?Eg@8u?`nc|YGKL(QNGXbAs&xVY!j4`94voo8B*rE0CYID9 zKgkH|-~o$FlC42Di3$n>f4#Au)SJldeQagXlG>8xJ5jmvkmVr*jVrunRuY4Y`{X8K zTMo*@Nmxt4`Os-1r%@jMI*?=rvci*yF&}g18z}uio}PAB04=T;fviUCnZ|olfBmy=3|sf7N&ifZQi-p@S-s3e~Vw zg}T0}plOwAiLgsw#wr&jzwqFe@fNVV#6p04_Y^`rG?_TOGulzK6DM0y# z$EKskzNv)|m#V(hH=SDrlh1@JT2`6 zbzF?NB4Hr<(&sKPsjKH&Ew?{=6d&Yx+sfjLOt|tfCPv-e=gt)_swwF--`k2?4|F(R zmU*j+Dzw)>G|?EV;enU~FwG5%)*N|`^ zBx&w#!}+tE#Oj>aRnl&_lh6>n<-scvd$`X**s=8tk24^kbUeSx*Ex?Gc-J9Uckc>@ zC2j4U7+tnK0#?S)QWDWwTuNDv-{sn5+ZcZIn$KeRs}y_@>Mjk7nvz=Mk`G-$MDMh( zRZAML@5C4sN6a|#{avEv(VZmEb+P?XSa^C={lz%ln`8G#2d&`q@BJP52+!47=W^wA z)V^OqTyL44%vG#iu?L5viBN@idnRb@JNzJ;kdnb4T(Lz+-Om69IFZF(KC-P8muhRu zoy$yX%^6qJ)ed>|)qhEXZEqsv*>3xtHhBTD70k8Ol-~rALbF-kwhlwm9UnP2l zq7ssz(Lt|#rW?{FzKg7Okna0{Tzqbu0p}X z`8-qcwN$o|odtB;J)E~BRznOG^NI~~^FHL=$ z?n_^Fl2Ti@W#wekPbU~-!j0J8AKo*ljuBpK=tTz02MdYEdI99c&Vnc6cNuK+(w7a? z;BC}u%~I(D=UWVO8@E|@fw2-c^pU0;ZuR%@YY(JFES@M&lx2GYn>J+!GlFENEZ75i zI2Xf3>9awGt7(k{x|k&J#vcTzH=3hP?^)W%;_vI(ic2wN5h9iYPQ)M7AxO?V2xbYO&UyYt=zOb910d@d4;s z#fu_ZnZjG6YmgzU%T5&~YK6yQlU3S(8sy&E;RJZ3;N|F4(LeQ!aJ34SHeJeAGRlxF zY{ZKQ;Ts=5?4yxZ{2IfYrn6EhXb^8blmbFK zHdfh#eBI;e>7RxnOh78}cZgqpQorDvytY;>Q5i1n?8?}kiA=%XDd6eFqaQcVsZBIV zZ8!N;;!;-B%p&^he(}W9fJ^thpZGakiT0bgd1E~fTm;(Re1pWxN=fF4rZHM=u}qlC zq54n!NUpLSA~))6+V0Yd+nas6Vk<9I(rTP=Fdx*wDmA5_{sfD z6SeUSJv*w}pKVs;sG6-CX`apkpphSteHU#W!@o|Cp`!>VjhJ))=+y?==jW&pm6qCysl zIGd%VUhbudS{K;*$w)TLa_t6D+bPK{XKmDIqN$J+WSbuJzSP;Q`5GW_#Y*%_2=$w` z3wKv80zL?@H1yytm|qUxXe+<5OGVj-;|95&4SSA*M^yxhX|%)g83BNN>dVIzUGx9c zm>aH`S7JOo-h-d)^v*6uabuUZ7rJ0VfWCymJf+^w&d{F8${qlo z0ZJlEs$1asiB9dakXy>!0Nb~_i>E2)mu-b8;@HrFa{_4bv9pJ|w0r+3ZYtX=%isW< zJ96QJ`o9EpPid{7j*iZ$-N6ro_bWNw>V90}body=RE>A;x7Jj4)a#p7Eh*&~CsbC- zzP=O<_6B=nod-5OQP<}C3{0W89P)je-Ysj87@3u;OZdqr)1XK+hs=Cgz$~MWIh*?& zBy--@Ey}ulSbX!&r7=E8zVEu;kHY08$KE57BqWm!su;DC9pdEwKZL&xAYFr7305~I za4OvxQ00}U6bd6B=bU>shn!Buhj4w+%~;;=*d{iyHo{KYp$DXYW>Yej$3;jR`w*W@ zyT)4B8}<+M4eSHUjW={+ZA{ORc8x7CoXu$zZeh=M`pg$Z79eab*cTM1kjI@1p|+Xx z30k4Am8!^Wp5mtgVv$_%)t@6DYc|ooeDci0V19gtMdd=0XwJOsiC@;fcYC|9`^P0k z4iwimZc#qZXkSo%W;p4j0WJ}?W1%u@0Yr|bTE`wnl8Lta|4D2qcx8G5!yuyQ(xc;eSOQ%5lT(KP0o4aY?Cwc2O(N7^qF>TGQAn~4WzdV+&8 zOd!E+T-nB2YcJ7dnfGY=NjEyiz04y3@pnpbA83o0%FT%nv$}z+ULK8 zJ@vyz7PZtgJfO&|=HaWBQtwN9u#Wqxa(Na?GurTro6~6l&#`n8YPb% zv0gNPq=48sPRJm+MuztrB(j{RDkK=)=_&)T&y~lyQ4Ah&8*lZk><-^OR zctQ;tYVBKJ5Rh;ONk_SP8K!lWgUMz4eaOHh&!}+5lZKqNK)OPbh~Ge=u(6vpSELL= zgwvsCs;V0i6~(oSU{-o39dqGZn|qDhg_w=AcvW_|zdumNq=c_3(^!1a{p5IfB4zZn z^0#GhL{DxPwCcUEut(mW<(*77^9QC2=<$Yz?(Dg67SxM1h%c`{XeaB>INyjh=5{@! zyYxXsGVYCJ@CqTshEUynzn3EbrZ7;t%9Ay9^fX%hom=_+&f-8Kb+*>dUz$dA+*UV zG}{?ilILjN#W(i&qFD9hd|cfcaKeUYBR(J{+BtPiC57l~*>?+m4g38g-U+2C2Fb1M z_YNi+j=6bmCAq1kSgkgQ>V-StW^Tx^!JBLNqWF~A04qBiO-4z?mfOA{KBNp@p@A1; zjk}fngk)o69XDR|0@bla)N4e&C|%Xe7B{IEs91{VmIqs&Oy#*WNi4~5Ax45&+@rQ< z)k9g58?-(s9>kC-)lFHIk`6jDJ^p_>m2Mom6=Y%k!4 z2TzIPsmlK0UZ=?)X~1t6Ev{W6eYnFDy(-F{Cdt*7AKe6M*X*q41JWgLRDs{gp74oR z*PPM>EVagaXjyE!o78l#c5Z!>otghr=DOi|=LlzFl2qcv`Kza=_Y1 zBA*URsoPqHrt(c%gk99E+*#Tj$%L7UR=`}n2jBnpqNuNHFG(GCM*3gVtW+$G;cyK} zzOFtVu9UAl8_$78R8+v1HsC%j%Mp)-O@I{O?QVvTg=_<~BZ?!O5WfTNP98}b_Wfz_ zxS4H^QjcihP5!dzU$?Snd-QU@(v&u}LORQ*Uda(TTC8Uy>VzCFKec7wFr$9E8sx9$ zoc~i1dVJq&_UsYGn|L^wt3YyiE0=%3X(`mR$664_V$`qxRt735LzijUMbp3Pquf7N z$7+110@RBDq6B*rz~mYM7=>*&J3{qkU7riR4rX z$A$iSRwdaIucv8Qmnp_l;q!>rK!*>u-g_tZdL5_NiK(i2*;brV1pz#?lX3ySct1gV zdPspzHoZ^E3ebTh&oubT%Gpn({%e_t%u|ZKC7n53dWxe`>*Rpum z=o!$f*DkrOH|`RFRdh5()!FT;a3E)0QgogqUz63h(>g2to*Mu_+dugHz{c6pXoif= zCdE53mlBk(7ro8J7eWr4M)xayL~vga@Ay{OM&Zke6r@;Vr2#f%_fEbe)D-!rgZ}wg z&V2{{IX2#ax@=qjg`PK}R+R5$U1z7`Iu#)7OP_I$BKXWxN0~Q{U*gV)h;FKhfm82t zjT*vjGDk<^I$d~4!CMpKh|OL0T+H$s>%OJSUL}$h`lQoR6ggKf0wESZcw&ty0XD3( zx-|;52Z~Sn8`YvlYGm>!lh1dksuC|!%*phs{3`!(#`rhY-|+t+*1R)4q{g+X31{~+ z3B@v=r*i^Y5mM$sN!ERtJZCk#ur%R}H>b^!b&L2dLxu!Z|Gop)EoO?0uju$bkC_tv zG0I`ca`+;Rv2FcuP4E)XWj!L$Vw4?lQ28x?@a!{r3UQb?o!G$;{5e|E@p7d21l`3p z+iy2sg(B`Fb0gm~Q_;2?{b^~9E9aTLOK;IkAW)g?f>$ZdLiE)J2c2+U+`-v8^8OMlrZ*3aOqAbB$A80RUoE z6q)eX{HAYnDZwAKvd7tc~5+to0iMm3q}Ig?ko^6qmwUn(c56H;t`R z{Y6jyc`>yea{ydB;P+XNslZ52dlmA@%p{*RV232)w&keADb{HI8_*mk5#DVXR@bwo zc3wGK<-x9t0`(a4-q@{Uui@30-!1cJck=v0*+yV8;6!Yy`jeZizK9t#`z-hq!?FHr zabe+aF_qF_T$2qw)CTmelHCp6O1uu+xV6V#ss$_2O3heoM$2l1u~L-1;S&WJ8CTrd zGY8=ligN~r*FhY|sMRNdM&&VC88C{Y5pb@jWLiA=slb-g`8XGfv)=_jmz?AhRQfrd`~yh6dKcl!wJ=9-+ZC>EnC>9 zPVKF;Si4%8-Si9ki7JHAB4y%^a()4wCrLElFH*#^*qBvILu6}${KF@8EvSoafqz#> zXX)Men=~^M!=H((8*z$)J|!7WIpS_OY1%!x&)jcn4H+`XqfEqFSY3?kFO%kuV^;IJ zS}tT@3m1LBDu{R8hZmYhpp)a;8y_y!zDEtq7hieLOPOrhzsXvaEyQSO!r6>U z2@<@}(^?{DqIE9vn_+2$n#s;$w?c`Dd2>zht|bIo*o)+jeD1bI?^ZpRA8i-^ z4~_anf+R@k zx!H4=`}w7?puLr0UREE0i(!a-R@cxo61K&uFbP9z`}yzXMjuq)=KAim%LOXibb~xa zG_DcV)0?K$Q|mH#@|OZh*Nuk%Za)7%;Q8OlbuQP>Kh4o?K{fa+#IuEI!t6FMdY_y+l>YcN*pfN)QT z+#e&a&*8gBiFCj_G3ouH2NT4@Dqdqp@`<|VKx2Q&EW%;;)H~iuv8Htetvd5*|Q5YSvAIhl zsg`lzjn7E4Knalo<7X(w@5w^eVEXb~*5fX}rdiP3Q;0t=VFtk`{)2xb%Wnu}jTOHzyeng_cWPmv-@$$+gzIpCn^b<1+``QUkasO3xP z0)lX3-%ki}l71qI7X<{mShAg|1LY(ZESgfvB*2pc52iloky^_SiMaV10aHHBNB5tx zLHZ)mzVcD)6kBsDL2&L+eMy$lhXbP(T6>vYB6Ch`T#0kPlD$SrkNLBVhG~FmJdayd zX5G!h6c%Ofu#c-~w6bMERN$Q9L@F^8=8Yj#tn?#za2oi?35Ar{Eo+-J>}O!U*=RAx zJN8O6+CPX%&^by;b~?c`xH4dmXKsGbs?pmuMSeW0}C?4Faw9*igB-X zOY}oDX3|$g5EYZ3uf+G4@wp5PyDBKqOlAG7mqZf859N*?dX-&si4 z&)UC-%hc|XvFJVud#6T;B!MrTpkX`aj1Z@`ZR!nf%2<6$o3Ce$C8dl$c0ZP9`-sma zwt>}(dsH*=>3xLl>_74k7t__9JmLIf!1LEPr#)&TzYmyO7B)Tb#Vs&8uf3&(D&Tga zW#NEhs~q^onfuA)(8S)Xc(I3IHwQ~H`HE-Ffg1!RxQG3mp;8qUjcrB5$vSuF2FNG> z=?X|Ce_BpwF5=6AfCH+TaA^aT9@X*)Sj5fVLLmFiv4gtYS&1%&SoeM9Jg~@&S8Hxh z_WSB4E(-9Zfopd^xFL@Ch-fE}N7;&|oXtssYGWRVwWN2i%Pjz~8fxa&TUNdeh_SPb zXjt10c6Q0Kdr_c|l8`^B>5DvO`W1KApu5uS3AU_x#%Be?omupiYN+ zLMbyEG@H>8TpQI{N)DZ`#9vzJ=Yg-n9>kedniz;!)mEG(tAjNCTUkHY)I*ux8JISt znBvOi(mf-qYj$c}MW)^gMHzsP?+c7*?sC>Ua%Pd?T8hZ~IaJ0d!M{nXEutd_A{KFKyN9a4%<0P_cRK4YwmUri zZi^XBd+$<$%f%qcHB74J5WP@TXz2`O>^{;y))sQ+8rBf?P>CQVQPCr{8QZV2}#)0~xHgAC3=wOu@v$JZ--6PrB-m zU;6Ymg$Z3AVi@zwg?Y2FOME1S6V)T+&gR0R)OBu7U7TsVF02QjYYEr@YUl*AjbL?Y zLzI@RojMJH`J#qtQ7hM>Iv3{X z9aaB9P%Y%?_yezk#iFt;A7l}yt~1#GgoHCc(7*0ISTPQ_{h z13sBm`xLh3FeGKS!Q$=wE{_m>p1_^)l~+71M-t7F%yR zcHtd2{v#M&Bivm}ERVWV%4DXwaGOG3>X&Gt$0M#u_!sgz2nUVUj~%9Z2w6?6(hvhr z1fYx2tpb9HtZSm9Fp?i~`WXK#-hL`EyVWr97ffe%X0ONAkgbFjudh-L-ELobdMb_XRC&%XE zs%0cd#%yEEPNHDR(P7m8bwTG;aCR!=uQ#<6f_)bGBU}+0T^o{@x6}EVKFQ?1t&fC@ zLC7;HO!AF5@Z1e*%}Tjj{a1WP=OxBK-;(XeCI$W*W1$;(tb^t;G8PB2P3`|1##*VQ z5w80Jxg-51ldSeVINNGY1E>f{&+g|41OT(-Kb^Ln3J9%Wp8e_+6j_m-9A~ic?%wN(SW%(?k!_Y0&pXGS~DkJn5MhV{&s!f zm;BFH6v@Xtd8nL+`s_IKfW}LViiNIO%J;sPwti^S=@TATbWm-Lv?^*q9#{VotMEi2 z*~g8|tZF)Urkhka(a)rSk047RU_)ukjs2Njn;OcrLzldVPuU?HnAF#Qy4*9Nx;CcO z%ep=}1qV3jfOePA$WwNTuz%5fE#GS0#|Ul|fSj~S(y z;3MuNV|B*sOg$vrS-`&hyVzD*#m(0rTI6P;bJC197TRPKJ%aW_OqM;b{4I3p9yZdu zMQt88+-*sUYqmoK>l`x|m<$=G5La6+Cl~Ug#&I8cRA!h{QtE}XDy1z-`e-f~Pgo!$ z+B^nyLpWGQw@!132ur@d!ntVYImlYw#N^k%^XL;3KEo|fF#Sw;KA+CA?&Q9(@~NUe z?JSQ((d&`quCEIjX|2VW`=wCq>fi#((}2gRY*nBzzyNRE_^I0okuOmnN!X=D>- z#tMFuh)s0(>0j{)!l;*!qx2w7rgNl1W!&50g9KUo2(8YIy+yI!7Pol1e$ht|;z7yZ}VNZ$l#)K-*Fgxc)2nw75mi5x=%&7lLE;? zb)8V&u9IQdpvi%0Van0feli5Lis7hSlp9$-Pw&?@KL}xKPPzyXlYWF-9w}>F60+!% z(%6uMvR1S%pZ@zFhAEK(cwXOeNh6Vzx{| literal 0 HcmV?d00001 -- Gitee From 02c53f14f5ec228bd6308ab0ba0da4f049591374 Mon Sep 17 00:00:00 2001 From: "Shine.Wang" Date: Thu, 24 Oct 2024 15:25:26 +0800 Subject: [PATCH 2/4] add comments --- hwcompatible/.idea/.gitignore | 8 ++ hwcompatible/.idea/.name | 1 + hwcompatible/.idea/hwcompatible.iml | 12 +++ .../inspectionProfiles/profiles_settings.xml | 6 ++ hwcompatible/.idea/misc.xml | 7 ++ hwcompatible/.idea/modules.xml | 8 ++ hwcompatible/.idea/vcs.xml | 6 ++ hwcompatible/client.py | 11 ++- hwcompatible/command.py | 5 +- hwcompatible/command_ui.py | 60 ++++++++++----- hwcompatible/common.py | 13 ++-- hwcompatible/compatibility.py | 77 +++++++++++-------- hwcompatible/config_ip.py | 7 ++ hwcompatible/device.py | 62 +++++++++------ hwcompatible/document.py | 56 +++++++++----- hwcompatible/job.py | 48 ++++++++---- hwcompatible/log.py | 11 ++- hwcompatible/reboot.py | 39 +++++++--- hwcompatible/sysinfo.py | 9 ++- hwcompatible/test.py | 19 +++-- 20 files changed, 326 insertions(+), 139 deletions(-) create mode 100644 hwcompatible/.idea/.gitignore create mode 100644 hwcompatible/.idea/.name create mode 100644 hwcompatible/.idea/hwcompatible.iml create mode 100644 hwcompatible/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 hwcompatible/.idea/misc.xml create mode 100644 hwcompatible/.idea/modules.xml create mode 100644 hwcompatible/.idea/vcs.xml diff --git a/hwcompatible/.idea/.gitignore b/hwcompatible/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/hwcompatible/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/hwcompatible/.idea/.name b/hwcompatible/.idea/.name new file mode 100644 index 0000000..7c3af41 --- /dev/null +++ b/hwcompatible/.idea/.name @@ -0,0 +1 @@ +compatibility.py \ No newline at end of file diff --git a/hwcompatible/.idea/hwcompatible.iml b/hwcompatible/.idea/hwcompatible.iml new file mode 100644 index 0000000..07abf20 --- /dev/null +++ b/hwcompatible/.idea/hwcompatible.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml b/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/hwcompatible/.idea/misc.xml b/hwcompatible/.idea/misc.xml new file mode 100644 index 0000000..db8786c --- /dev/null +++ b/hwcompatible/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/hwcompatible/.idea/modules.xml b/hwcompatible/.idea/modules.xml new file mode 100644 index 0000000..3063810 --- /dev/null +++ b/hwcompatible/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/hwcompatible/.idea/vcs.xml b/hwcompatible/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/hwcompatible/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hwcompatible/client.py b/hwcompatible/client.py index fb64966..e670602 100755 --- a/hwcompatible/client.py +++ b/hwcompatible/client.py @@ -24,6 +24,13 @@ class Client: """ def __init__(self, host, oec_id, logger): + """ + Attributes: + host (str): The host address of the server. + oec_id (str): An identifier associated with the upload. + logger (Logger): An instance of a logging utility. + form (dict): A dictionary to hold the form data for the upload request. + """ self.host = host self.oec_id = oec_id self.logger = logger @@ -41,7 +48,7 @@ class Client: job = filename.split('.')[0] with open(files, 'rb') as file: filetext = base64.b64encode(file.read()) - except Exception as excp: + except Exception: self.logger.error("Get files which need to upload failed.") return False @@ -67,6 +74,6 @@ class Client: self.logger.error("Upload file to server failed. %s" % res.msg) return False return True - except Exception as excp: + except Exception: self.logger.error("Upload file to server failed.") return False diff --git a/hwcompatible/command.py b/hwcompatible/command.py index 26ba701..6ee4f57 100755 --- a/hwcompatible/command.py +++ b/hwcompatible/command.py @@ -21,7 +21,10 @@ from .constants import SHELL_ENV class Command: """ - Creates a Command object that wraps the shell command + Creates a Command object that wraps the shell command. + + Attributes: + logger (Logger): A logging object used to record logs. """ def __init__(self, logger): diff --git a/hwcompatible/command_ui.py b/hwcompatible/command_ui.py index 31112a8..841534b 100644 --- a/hwcompatible/command_ui.py +++ b/hwcompatible/command_ui.py @@ -20,18 +20,26 @@ from .constants import SAMEASYES, YES, SAMEASNO, NO class CommandUI: """ - Command user interface selection + Provides methods for command-line interface interaction such as prompting the user for input, + reading integers, getting confirmations, and editing values. """ def __init__(self, echoResponses=False): + """ + Initialize the CommandUI object with an option to echo responses. + + Args: + echoResponses (bool, optional): Whether to echo the user's responses. Defaults to False. + """ self.echo = echoResponses @staticmethod def print_pipe(pipe): """ - print pipe data - :param pipe: - :return: + Print data from the given pipe until there is no more data. + + Args: + pipe (file-like object): A pipe-like object to read from. """ while 1: line = pipe.readline() @@ -42,10 +50,14 @@ class CommandUI: def prompt(self, question, choices=None): """ - choice test item - :param question: - :param choices: - :return: + Prompt the user for input with an optional set of choices. + + Args: + question (str): The question to ask the user. + choices (list, optional): A list of possible choices. Defaults to None. + + Returns: + str: The user's response stripped of leading/trailing whitespace. """ while True: sys.stdout.write(question) @@ -63,10 +75,14 @@ class CommandUI: def prompt_integer(self, question, choices=None): """ - choice test item - :param question: - :param choices: - :return: + Prompt the user for an integer input with an optional set of choices. + + Args: + question (str): The question to ask the user. + choices (list, optional): A list of possible choices. Defaults to None. + + Returns: + int: The user's response converted to an integer. """ while True: sys.stdout.write(question) @@ -86,9 +102,13 @@ class CommandUI: def prompt_confirm(self, question): """ - Command interface displays confirmation information - :param question: - :return: + Prompt the user for a yes/no confirmation. + + Args: + question (str): The question to ask the user. + + Returns: + bool: True if the user's response indicates 'yes', False otherwise. """ while True: reply = self.prompt(question, (YES, NO)) @@ -101,10 +121,12 @@ class CommandUI: @staticmethod def prompt_edit(label, value, choices=None): """ - prompt choice edit - :param label: - :param value: - :param choices: + Prompt the user to edit a given value with an optional set of choices. + + Args: + label (str): The label to display next to the input field. + value (str): The initial value to be edited. + choices (list, optional): A list of possible choices. Defaults to None. :return: """ if not value: diff --git a/hwcompatible/common.py b/hwcompatible/common.py index e5fb9bc..eb2c388 100644 --- a/hwcompatible/common.py +++ b/hwcompatible/common.py @@ -100,7 +100,7 @@ def __create_testcase(test, logger, subtests_filter): def copy_pci(): """ - copy the PCI file if it exists + Copy the PCI file if it exists and its content differs from another specified file. """ if os.path.exists(CertEnv.pcifile) and \ not filecmp.cmp(CertEnv.oechpcifile, CertEnv.pcifile): @@ -111,10 +111,12 @@ def copy_pci(): def discover(testname, logger, subtests_filter=None): """ - discover test - :param testname: - :param subtests_filter: - :return: + Discover and load a specific test module and extract a test class that meets certain criteria. + + :param testname: The name of the test module to discover. + :param logger: A logger object for logging messages. + :param subtests_filter: An optional filter to apply to subtests. + :return: An instance of the test class if found, otherwise False. """ if not testname: logger.warning("Testname is not specified, discover test failed.") @@ -127,6 +129,7 @@ def discover(testname, logger, subtests_filter=None): dirpath = sublist[0] break pth = os.path.join(dirpath, filename) + # check whether the file can be read. if not os.access(pth, os.R_OK): return False diff --git a/hwcompatible/compatibility.py b/hwcompatible/compatibility.py index 406aac5..d9df5ac 100755 --- a/hwcompatible/compatibility.py +++ b/hwcompatible/compatibility.py @@ -39,32 +39,35 @@ class EulerCertification(): """ def __init__(self, logger): - self.certification = None - self.test_factory = list() - self.devices = None - self.ui = CommandUI() - self.client = None - self.dir_name = None - self.logger = logger - self.command = Command(logger) - self.category = '' + """ + Initialize the EulerCertification object. + """ + self.certification = None # Certification document object + self.test_factory = list() # List of test factory objects + self.devices = None # Device document object + self.ui = CommandUI() # User interface for command line interactions + self.client = None # Client object for communication + self.dir_name = None # Directory name for packaging logs + self.logger = logger # Logger instance + self.command = Command(logger) # Command execution helper + self.category = '' # Test category (e.g., 'compatible', 'virtualization') def run(self): """ Openeuler compatibility verification :return: """ - self._select_category() + self._select_category() # Select the test category if self.category == "virtualization": self.logger.info( "The openEuler Virtualization Test Suite") elif self.category == "compatible": self.logger.info( "The openEuler Hardware Compatibility Test Suite") - copy_pci() + copy_pci() # Copy PCI device information certdevice = CertDevice(self.logger) - self.load() + self.load() # Load existing certification data while True: self.submit() @@ -75,15 +78,15 @@ class EulerCertification(): oec_devices = list() if self.category == "compatible": - oec_devices = certdevice.get_devices() + oec_devices = certdevice.get_devices() # Get devices for certification self.devices = DeviceDocument(CertEnv.devicefile, self.logger, oec_devices) self.devices.save() - test_factory = self.get_tests(oec_devices) + test_factory = self.get_tests(oec_devices) # Get the list of tests self.update_factory(test_factory) if not self.choose_tests(): return True - test_suite = create_test_suite(self.test_factory, self.logger, self.category) + test_suite = create_test_suite(self.test_factory, self.logger, self.category) # Create the test suite args = argparse.Namespace( test_factory=self.test_factory, test_suite=test_suite) job = Job(args) @@ -92,20 +95,19 @@ class EulerCertification(): def run_rebootup(self): """ - rebootup - :return: + Handle the reboot process and resume testing. """ try: if not os.path.exists(CertEnv.rebootfile): return True - self.load() - test_suite = create_test_suite(self.test_factory, self.logger) + self.load() # Load the existing certification data + test_suite = create_test_suite(self.test_factory, self.logger) # Create the test suite args = argparse.Namespace( test_factory=self.test_factory, test_suite=test_suite) job = Job(args) reboot = Reboot(None, job, None) - if reboot.check(logger=self.logger): - job = reboot.job + if reboot.check(logger=self.logger): # check the reboot state and verify the reboot time + job = reboot.job # Update the job object job.run() reboot.clean() self.save(job) @@ -138,6 +140,8 @@ class EulerCertification(): :return: """ os.makedirs(os.path.dirname(CertEnv.datadirectory), exist_ok=True) + + # Create new document object with hardware and OS information. if not self.certification: self.certification = CertDocument( CertEnv.certificationfile, self.logger) @@ -151,7 +155,7 @@ class EulerCertification(): factory_doc = FactoryDocument(CertEnv.factoryfile, self.logger) self.test_factory = factory_doc.get_factory() - oec_id = self.certification.get_certify() + oec_id = self.certification.get_certify() # The test ID that you provided hardware_info = self.certification.get_hardware() self.client = Client(hardware_info, oec_id, self.logger) version = self.certification.get_oech_value("VERSION", "version") @@ -169,13 +173,15 @@ class EulerCertification(): def save(self, job): """ - collect Job log + Collect Job logs and package. :param job: :return: """ - doc_dir = os.path.join(CertEnv.logdirectoy, job.job_id) + doc_dir = os.path.join(CertEnv.logdirectoy, job.job_id) # create the directory of logs. if not os.path.exists(doc_dir): return + + # copy the temporary log files to log directory if self.category == "virtualization": FactoryDocument(CertEnv.virtfactoryfile, self.logger, self.test_factory).save() shutil.copy(CertEnv.virtfactoryfile, doc_dir) @@ -185,6 +191,7 @@ class EulerCertification(): shutil.copy(CertEnv.devicefile, doc_dir) shutil.copy(CertEnv.certificationfile, doc_dir) + # rename and compress the logs package cwd = os.getcwd() os.chdir(os.path.dirname(doc_dir)) self.dir_name = "oech-" + datetime.datetime.now().strftime("%Y%m%d%H%M%S") \ @@ -200,6 +207,7 @@ class EulerCertification(): self.logger.info("Log saved to file: %s succeed." % os.path.join(os.getcwd(), pack_name)) + # copy logs package for submit process shutil.copy(pack_name, CertEnv.datadirectory) for sublist in os.walk("./"): for dirname in sublist[1]: @@ -209,9 +217,10 @@ class EulerCertification(): def submit(self): """ - submit last result + submit request of last result :return: """ + # Get the logs package and sorted packages = list() pattern = re.compile("^oech-[0-9]{14}-[0-9a-zA-Z]{10}.tar$") files = [] @@ -233,7 +242,7 @@ class EulerCertification(): self.logger.info( "Upload result to server %s succeed." % server) time.sleep(2) - + # clean the copied logs package after submit process for filename in packages: os.remove(os.path.join(CertEnv.datadirectory, filename)) @@ -263,6 +272,8 @@ class EulerCertification(): sort_devices = self.sort_tests(devices) empty_device = Device(logger=self.logger) test_factory = list() + + # from test scripts get the test name casenames = [] test_path = os.path.join(CertEnv.testdirectoy, self.category) for (_, dirs, filenames) in os.walk(test_path): @@ -296,6 +307,7 @@ class EulerCertification(): test["driverVersion"] = test.get("device", "").get_driver_version() test["boardModel"], test["chipModel"] = test.get("device", "").get_model(testname, file) test_factory.append(test) + # the tests don't need physical device elif testname in NODEVICE: test = dict() test["name"] = testname @@ -312,7 +324,7 @@ class EulerCertification(): def sort_tests(self, devices): """ - sort tests + sort tests from devices list :param devices: :return: """ @@ -465,7 +477,7 @@ class EulerCertification(): def show_tests(self): """ - show test items + show test items in UI :return: """ device_info = namedtuple('Device_info', DEVICE_INFO) @@ -507,7 +519,7 @@ class EulerCertification(): def show_virt_tests(self): """ - show virtualization test items + show virtualization test items in UI :return: """ self.logger.info("\033[1;35m" + "No.".ljust(4) + "Run-Now?".ljust(10) @@ -563,7 +575,7 @@ class EulerCertification(): def check_result(self): """ - check test result + check whether all the tests were passed :return: """ if len(self.test_factory) == 0: @@ -575,7 +587,7 @@ class EulerCertification(): def update_factory(self, test_factory): """ - update tese factory + update test_factory :param test_factory: :return: """ @@ -620,6 +632,9 @@ class EulerCertification(): + device.version.ljust(18) + device.chip.ljust(20) + device.board, log_print=False) def _select_category(self): + """ + select category of certificate type + """ self.logger.info("Please select test category.", log_print=False) self.logger.info("\033[1;35m" + "No.".ljust(6) + "category".ljust(35) + "\033[0m", log_print=False) categories = dict(enumerate(TEST_CATEGORY)) diff --git a/hwcompatible/config_ip.py b/hwcompatible/config_ip.py index 3f24160..83f2d72 100644 --- a/hwcompatible/config_ip.py +++ b/hwcompatible/config_ip.py @@ -30,6 +30,13 @@ class ConfigIP: """ def __init__(self, config_data, logger, testcase): + """ + Initialize the ConfigIP object with configuration data, logger, and testcase. + + :param config_data: Configuration data dictionary. + :param logger: Logger object for logging messages. + :param testcase: Test case object + """ self.config_data = config_data self.logger = logger self.command = Command(self.logger) diff --git a/hwcompatible/device.py b/hwcompatible/device.py index 0322640..6597af2 100755 --- a/hwcompatible/device.py +++ b/hwcompatible/device.py @@ -29,8 +29,9 @@ class CertDevice: def get_devices(self): """ - Get devices information - :return: + Execute a command to get the device database from the system and parse it to create Device objects. + + :return: A sorted list of Device objects representing the devices found on the system. """ self.devices = list() cmd_result = self.command.run_cmd( @@ -87,16 +88,18 @@ class Device: def get_property(self, prop): """ - get properties - :param prop: - :return: + Retrieve the value of a specific property from the device's properties dictionary. + + :param prop: The name of the property to retrieve. + :return: The value of the specified property, or an empty string if not found. """ return self.properties.get(prop, "") def get_name(self): """ - get property value - :return: + Determine the name of the device based on available properties. + + :return: The name of the device derived from INTERFACE, DEVNAME, or DEVPATH. """ if "INTERFACE" in self.properties.keys(): return self.properties.get("INTERFACE") @@ -108,8 +111,11 @@ class Device: def get_model(self, name, file): """ - get board model name - :return: + Populate the device's board and chip attributes based on the type of device and PCI information. + + :param name: The name of the device (e.g., 'fc', 'gpu'). + :param file: pci.ids file. + :return: A tuple containing the board and chip model strings. """ self.name = name self.file = file @@ -154,7 +160,7 @@ class Device: def get_raid_card(self): """ - get the board model and chip model of raid card + get the board model and chip model of raid card. """ flag = 0 for ln in self.file.readlines(): @@ -176,7 +182,7 @@ class Device: def get_fc_card(self): """ - get the board model and chip model of FC card + get the board model and chip model of FC card. """ flag = 0 for ln in self.file.readlines(): @@ -198,7 +204,7 @@ class Device: def get_nvme_card(self): """ - get the board model and chip model of nvme card + get the board model and chip model of nvme card. """ flag = 0 for ln in self.file.readlines(): @@ -226,7 +232,7 @@ class Device: def get_nic_intel(self): """ - get the board model and chip model of intel card + get the board model and chip model of intel card. """ flag = 0 for ln in self.file.readlines(): @@ -249,7 +255,7 @@ class Device: def get_nic_broadcom(self): """ - get the board model and chip model of broadcom nic card + get the board model and chip model of broadcom nic card. """ flag = 0 for ln in self.file.readlines(): @@ -267,7 +273,7 @@ class Device: def get_nic_huawei(self): """ - get the board model and chip model of intel card + get the board model and chip model of intel card. """ flag = 0 for ln in self.file.readlines(): @@ -287,7 +293,7 @@ class Device: def get_nic_netswift(self): """ - get the board model and chip model of netswift card + get the board model and chip model of netswift card. """ flag = 0 for ln in self.file.readlines(): @@ -305,7 +311,7 @@ class Device: def get_broadcom_card(self): """ - get the board model and chip model of broadcom card + get the board model and chip model of broadcom card. """ flag = 0 for ln in self.file.readlines(): @@ -333,7 +339,7 @@ class Device: def get_nic_mellanox(self): """ - get the board model and chip model of mellanox card + get the board model and chip model of mellanox card. """ flag = 0 for ln in self.file.readlines(): @@ -351,7 +357,7 @@ class Device: def get_gpu_card(self): """ - get the board model and chip model of gpu card + get the board model and chip model of gpu card. """ flag = 0 for ln in self.file.readlines(): @@ -369,7 +375,7 @@ class Device: def get_driver(self): """ - get the driver name of the board + get the driver name of the board card. :return: """ self.get_pci() @@ -385,7 +391,7 @@ class Device: def get_driver_version(self): """ - Get the driver version of the board + Get the driver version of the board. :return: """ if not self.driver: @@ -403,7 +409,7 @@ class Device: def get_pci(self): """ - get the pci of card + get the pci of card. :return: """ self.pci = self.properties.get("PCI_SLOT_NAME", "") @@ -415,7 +421,7 @@ class Device: def get_quadruple(self): """ - get quadruple by pci number + get quadruple by pci number. :return: """ cmd_result = self.command.run_cmd( @@ -431,6 +437,11 @@ class Device: return self.quad def get_cpu_vendor(self): + """ + Retrieve the CPU vendor ID from the system's CPU information. + + :return: The vendor ID of the CPU or "Unknown Vendor" if not found. + """ cmd_result = self.command.run_cmd( "cat /proc/cpuinfo | grep vendor_id | head -1", log_print=False) if cmd_result[0] == "": @@ -441,7 +452,7 @@ class Device: def _is_null(self): """ - Judge whether the board model and chip signal are empty + Judge whether the board model and chip signal are empty. """ if not self.chip: self.chip = "N/A" @@ -450,6 +461,9 @@ class Device: @staticmethod def _search_info(restr, line): + """ + Search for a pattern in the given string using regular expressions. + """ value = re.search(restr, line) if value: return value.group(1) diff --git a/hwcompatible/document.py b/hwcompatible/document.py index 85549d7..f5d9bbd 100755 --- a/hwcompatible/document.py +++ b/hwcompatible/document.py @@ -26,17 +26,26 @@ from .constants import FILE_FLAGS, FILE_MODES class Document(): """ - Basic document module + Basic document module. """ def __init__(self, filename, logger, document=None): + """ + Initialize the Document object. + + :param filename: The path to the JSON file. + :param logger: A logging object for logging messages. + :param document: Initial content of the document (optional). + """ self.filename = filename self.logger = logger self.document = document def save(self): """ - Save file + Save file. + + :return: True if the file was saved, False otherwise. """ with os.fdopen(os.open(self.filename, FILE_FLAGS, FILE_MODES), "w+") as save_f: json.dump(self.document, save_f, indent=4) @@ -44,7 +53,9 @@ class Document(): def load(self): """ - Load file + Load file. + + :return: True if the file was loaded, False otherwise. """ if not os.path.exists(self.filename): return False @@ -61,7 +72,7 @@ class Document(): class CertDocument(Document): """ - Get hardware and release information + Get hardware and release information. """ def __init__(self, filename, logger, document=''): @@ -71,7 +82,7 @@ class CertDocument(Document): def new(self): """ - Create new document object + Create new document object with hardware and OS information. """ try: cmd_result = getoutput("/usr/sbin/dmidecode -t 1") @@ -102,7 +113,7 @@ class CertDocument(Document): self.logger.error("The file %s doesn't exist." % CertEnv.releasefile) return False - + # Get the OS, kernel, ID, Product URL, server information sysinfo = SysInfo(CertEnv.releasefile) self.document["OS"] = sysinfo.get_product() + " " + sysinfo.get_version() self.document["kernel"] = sysinfo.get_kernel() @@ -115,7 +126,7 @@ class CertDocument(Document): def get_oech_value(self, prop, value): """ - Get oech version or name + Get oech version or name. """ config = configparser.ConfigParser() config.read(CertEnv.versionfile) @@ -126,45 +137,45 @@ class CertDocument(Document): def get_hardware(self): """ - Get hardware information + Get hardware information. """ return self.document["Manufacturer"] + " " + self.document["Product Name"] + " " \ + self.document["Version"] def get_os(self): """ - Get os information + Get os information. """ return self.document["OS"] def get_server(self): """ - Get server information + Get server information. """ return self.document["server"] def get_url(self): """ - Get url + Get url. """ return self.document["Product URL"] def get_certify(self): """ - Get certify + Get certify. """ return self.document["ID"] def get_kernel(self): """ - Get kernel information + Get kernel information. """ return self.document["kernel"] class DeviceDocument(Document): """ - Get device document + Get device information from device module. """ def __init__(self, filename, logger, devices=''): @@ -178,7 +189,7 @@ class DeviceDocument(Document): class FactoryDocument(Document): """ - Get factory from file or factory parameter + Get factory from file or factory parameter. """ def __init__(self, filename, logger, factory=''): @@ -193,7 +204,7 @@ class FactoryDocument(Document): def get_factory(self): """ - Get factory parameter information + Get factory parameter information. :return: """ factory = list() @@ -207,10 +218,15 @@ class FactoryDocument(Document): class ConfigFile: """ - Get parameters from configuration file + Class for handling configuration files. """ def __init__(self, filename): + """ + Initialize the ConfigFile object. + + :param filename: The path to the configuration file. + """ self.filename = filename self.parameters = dict() self.config = list() @@ -218,7 +234,7 @@ class ConfigFile: def load(self): """ - Load config file + Load config file. """ with open(self.filename) as fp_info: self.config = fp_info.readlines() @@ -231,7 +247,7 @@ class ConfigFile: def get_parameter(self, name): """ - Get parameter + Get parameter. """ return self.parameters.get(name, None) @@ -246,7 +262,7 @@ class ConfigFile: def add_parameter(self, name, value): """ - Add parameter + Add parameter. """ if self.get_parameter(name): return False diff --git a/hwcompatible/job.py b/hwcompatible/job.py index 5a3f3f8..ec68004 100755 --- a/hwcompatible/job.py +++ b/hwcompatible/job.py @@ -30,7 +30,7 @@ from .document import Document class Job(): """ - Test task management + Manages the execution of test tasks. """ def __init__(self, args=None): @@ -57,8 +57,10 @@ class Job(): def check_test_depends(self): """ - Install dependency packages - :return: depending + Checks and installs necessary dependencies for the tests. + + :return: True if dependencies are successfully installed, False otherwise. + :rtype: bool """ required_rpms = [] for tests in self.test_suite: @@ -83,7 +85,7 @@ class Job(): def run_tests(self, subtests_filter=None): """ - Start testing + Executes the defined test suites. :param subtests_filter: :return: """ @@ -113,7 +115,7 @@ class Job(): def run(self): """ - Test entrance + Test entrance. :return: """ self.logger.start() @@ -130,7 +132,7 @@ class Job(): def show_summary(self): """ - Command line interface display summary + Command line interface summary of the test results. :return: """ self.logger.info( @@ -150,7 +152,7 @@ class Job(): def save_result(self): """ - Get test status + Save the test status. :return: """ for test in self.test_factory: @@ -161,7 +163,10 @@ class Job(): def get_config(self): """ - get configuration file + Loads the configuration file(test_config.yaml). + + :return: True if successful, False otherwise. + :rtype: bool """ if not os.path.exists(self.yaml_file): self.logger.error("Failed to get configuration file information.") @@ -179,7 +184,10 @@ class Job(): def get_device(self, testcase): """ - Get the board configuration information to be tested + Retrieves the configuration information for the device being tested. + + :param testcase: The current test case being processed. + :return: Configuration data for the device or None if not found. """ types = testcase["name"] device_name = testcase["device"].get_name() @@ -196,10 +204,14 @@ class Job(): def _run_test(self, testcase, config_data, subtests_flag, subtests_filter=None): """ - Start a testing item - :param testcase: - :param subtests_filter: - :return: + Start a testing item. + + :param testcase: The test case to run. + :param config_data: Configuration data for the test. + :param subtests_flag: Flag indicating if subtests should be run. + :param subtests_filter: Filter for subtests. + :return: True if the test passes, False otherwise. + :rtype: bool """ name = testcase["name"] device_name = testcase["device"].get_name() @@ -261,10 +273,12 @@ class Job(): def _delete_case(self, test_name, logger=None): """ - Delete the test cases that have been completed from the reboot.json - :param testcase: - :param logger: - :return: + Delete the test cases that have been completed from the reboot.json. + + :param test_name: Name of the test case. + :param logger: Logger instance. + :return: True if deletion was successful, False otherwise. + :rtype: bool """ doc = Document(CertEnv.rebootfile, logger) if not doc.load(): diff --git a/hwcompatible/log.py b/hwcompatible/log.py index 608a0ce..577f553 100755 --- a/hwcompatible/log.py +++ b/hwcompatible/log.py @@ -23,10 +23,19 @@ from .constants import MAX_BYTES, MAX_COUNT class Logger(): """ - Log management + Logger class for managing log outputs to both files and the console. """ def __init__(self, logname, logdir, out, err): + """ + Initialize the logger with the given parameters. + + Args: + logname (str): The name of the log file. + logdir (str): The directory where logs should be stored. + out (file-like object): Output stream for console logging. + err (file-like object): Error stream for console logging. + """ self.logdir = None self.log = None self.stdout = out diff --git a/hwcompatible/reboot.py b/hwcompatible/reboot.py index 156c8ae..bfc3db4 100755 --- a/hwcompatible/reboot.py +++ b/hwcompatible/reboot.py @@ -28,6 +28,14 @@ class Reboot: """ def __init__(self, testname, job, rebootup): + """ + Initialize the Reboot instance with test name, job, and rebootup flag. + + Args: + testname (str): Name of the test. + job (object): Job object containing test suite and factory information. + rebootup (list): List of subtests that need to run post-reboot. + """ self.testname = testname self.rebootup = rebootup self.job = job @@ -37,7 +45,7 @@ class Reboot: def clean(self): """ - Remove reboot file + Remove reboot file. :return: """ if not (self.job and self.testname): @@ -52,8 +60,13 @@ class Reboot: def setup(self, args=None): """ - Reboot setuping - :return: + Set up the reboot process. + + Args: + args (argparse.Namespace, optional): Arguments from the command line. Defaults to None. + + Returns: + bool: True if setup is successful, False otherwise. """ self.args = args or argparse.Namespace() self.logger = getattr(self.args, "test_logger", None) @@ -63,14 +76,16 @@ class Reboot: return False self.job.save_result() + # Mark the test items that need to be restarted for test in self.job.test_factory: if test["run"] and test["name"] in REBOOT_CASE: test["reboot"] = True test["status"] = "FAIL" + # Save the status of the testfactory if not FactoryDocument(CertEnv.factoryfile, self.logger, self.job.test_factory).save(): self.logger.error("Save testfactory doc failed before reboot.") return False - + # Build restart test information reboots = list() for test in self.job.test_factory: if self.testname == test["name"]: @@ -89,6 +104,7 @@ class Reboot: if not Document(CertEnv.rebootfile, self.logger, reboots).save(): self.logger.error("Save reboot doc failed.") return False + # start the service command = Command(self.logger) command.run_cmd("systemctl daemon-reload") cmd_result = command.run_cmd("systemctl enable oech") @@ -101,8 +117,13 @@ class Reboot: def check(self, logger=None): """ - Reboot file check - :return: + Check the reboot state and verify the reboot time. + + Args: + logger (Logger, optional): Logger instance for logging messages. Defaults to None. + + Returns: + bool: True if check is successful, False otherwise. """ if not logger: logger = self.logger @@ -124,9 +145,9 @@ class Reboot: time_reboot = datetime.datetime.strptime(self.reboot.get("time"), "%Y%m%d%H%M%S") test_suite = self.job.test_suite reboot_suite = [] - for suit in test_suite: - if suit.get("reboot"): - reboot_suite.append(suit) + for suite in test_suite: + if suite.get("reboot"): + reboot_suite.append(suite) self.job.test_suite = reboot_suite except Exception: logger.error("Reboot file format not as expect.") diff --git a/hwcompatible/sysinfo.py b/hwcompatible/sysinfo.py index 40d0a7d..7cf9375 100755 --- a/hwcompatible/sysinfo.py +++ b/hwcompatible/sysinfo.py @@ -18,10 +18,15 @@ from subprocess import getoutput class SysInfo: """ - Deal system information + Deal system information. """ def __init__(self, filename): + """ + Initialize the system information object with a filename to read from. + + :param filename: os-release file. + """ self.product = None self.version = None self.kernel = None @@ -55,7 +60,7 @@ class SysInfo: def _load(self, filename): """ - Collect system information + Collect system information. :param filename: :return: """ diff --git a/hwcompatible/test.py b/hwcompatible/test.py index 41e58ec..40136c4 100755 --- a/hwcompatible/test.py +++ b/hwcompatible/test.py @@ -23,14 +23,17 @@ class Test: """ def __init__(self): - self.pri = 0 - self.requirements = list() - self.reboot = False - self.rebootup = None - self.args = None - self.logger = None - self.command = None - self.log_path = "" + """ + Initialize the Test instance with default attributes. + """ + self.pri = 0 # Priority level of the test + self.requirements = list() # List of requirements needed for the test + self.reboot = False # Indicates whether a reboot is required after the test + self.rebootup = None # Action to take after rebooting + self.args = None # Command-line arguments + self.logger = None # Logger instance for logging messages + self.command = None # Command object for executing shell commands + self.log_path = "" # Path to the log file def setup(self, args=None): """ -- Gitee From ef77223993aeb822dfd20304ff6720c754598856 Mon Sep 17 00:00:00 2001 From: "Shine.Wang" Date: Thu, 24 Oct 2024 08:21:46 +0000 Subject: [PATCH 3/4] update docs/design_docs/dev_design.md. Signed-off-by: Shine.Wang --- docs/design_docs/dev_design.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/design_docs/dev_design.md b/docs/design_docs/dev_design.md index 92db96d..54439a3 100644 --- a/docs/design_docs/dev_design.md +++ b/docs/design_docs/dev_design.md @@ -152,8 +152,11 @@ Mulan V2 ``` #### 3.1.2 功能模块分级 -![test_module](../pictures/test_module.png) - +|模块分级|模块| +|-------|----| +|核心功能实现|compatibility| +|主要功能模块|document、device、job、command、command_ui、common| +|次要功能模块|cert_info、client、config_ip、constants、env、log、reboot、test| ### 3.2 框架特点 -- Gitee From ab86f346bea4d1ba90c89052c4ae6b10352686fe Mon Sep 17 00:00:00 2001 From: "Shine.Wang" Date: Thu, 24 Oct 2024 08:24:29 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20hwco?= =?UTF-8?q?mpatible/.idea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hwcompatible/.idea/.gitignore | 8 -------- hwcompatible/.idea/.name | 1 - hwcompatible/.idea/hwcompatible.iml | 12 ------------ .../.idea/inspectionProfiles/profiles_settings.xml | 6 ------ hwcompatible/.idea/misc.xml | 7 ------- hwcompatible/.idea/modules.xml | 8 -------- hwcompatible/.idea/vcs.xml | 6 ------ 7 files changed, 48 deletions(-) delete mode 100644 hwcompatible/.idea/.gitignore delete mode 100644 hwcompatible/.idea/.name delete mode 100644 hwcompatible/.idea/hwcompatible.iml delete mode 100644 hwcompatible/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 hwcompatible/.idea/misc.xml delete mode 100644 hwcompatible/.idea/modules.xml delete mode 100644 hwcompatible/.idea/vcs.xml diff --git a/hwcompatible/.idea/.gitignore b/hwcompatible/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/hwcompatible/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/hwcompatible/.idea/.name b/hwcompatible/.idea/.name deleted file mode 100644 index 7c3af41..0000000 --- a/hwcompatible/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -compatibility.py \ No newline at end of file diff --git a/hwcompatible/.idea/hwcompatible.iml b/hwcompatible/.idea/hwcompatible.iml deleted file mode 100644 index 07abf20..0000000 --- a/hwcompatible/.idea/hwcompatible.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml b/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/hwcompatible/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/hwcompatible/.idea/misc.xml b/hwcompatible/.idea/misc.xml deleted file mode 100644 index db8786c..0000000 --- a/hwcompatible/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/hwcompatible/.idea/modules.xml b/hwcompatible/.idea/modules.xml deleted file mode 100644 index 3063810..0000000 --- a/hwcompatible/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/hwcompatible/.idea/vcs.xml b/hwcompatible/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/hwcompatible/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file -- Gitee