From 861d42cc6ac72a8e174afc47578187bfd22c2667 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 10 Aug 2018 15:47:12 -0700 Subject: [PATCH 01/90] Added automated creation of `testAuto.js` scripts. Code refactoring. Documentation update. --- tools/auto-tester/Create.PNG | Bin 12052 -> 14207 bytes tools/auto-tester/Evaluate.PNG | Bin 9334 -> 10981 bytes tools/auto-tester/README.md | 26 +- tools/auto-tester/TestRail.PNG | Bin 10972 -> 12712 bytes tools/auto-tester/Windows.PNG | Bin 9842 -> 11597 bytes tools/auto-tester/src/Test.cpp | 399 +++++++++++++----------- tools/auto-tester/src/Test.h | 17 +- tools/auto-tester/src/ui/AutoTester.cpp | 8 + tools/auto-tester/src/ui/AutoTester.h | 3 + tools/auto-tester/src/ui/AutoTester.ui | 116 ++++--- 10 files changed, 329 insertions(+), 240 deletions(-) diff --git a/tools/auto-tester/Create.PNG b/tools/auto-tester/Create.PNG index d82d4873a211e0a21e42a97f772c929da823cfeb..4a2a77d2f8f8888e393b3f66013a1e32e0db94c0 100644 GIT binary patch literal 14207 zcmeHtc~sI{yZ`rSHdxu9R;Qefk)ATOGILfcJ)x$QT4)X&kD6JMIi~_flbI=`rIk~I zMT$ZWffH~V6ivm_)C?78Q$$ik1q6P}@=SNF``-7y_x|2<&TswxV6E?F?fvX${5;Qo z_D($Fd}y`eW<>x1R@*~tT>wB10swN-8!2M49 z0iYPWa^6<~0Du+eARgfWp!90t_YEWDsXqXisMy=?cRTOH?Sf-L-M+ZViv6G0&8@9? z1Bu%W2QX{bUc9hM>j$q@aw@OwUSClSTDyE-)Pa)kY`^>FmFbJY=2+#dYtPvqn344;J+LM02fv*{Ehn#2LQmHCF=nI{KE)1c4Wln zcsIKD5O!v(CJHZlWFva?OcekwwBp0HP@e+PMA7?raq=Z;2VC4#n~NGDiasCekS64c zM#omd=MH>O1c0AMTg`CRlM}iGwpe0g`VlL6=`Fb1jMNEM002v!z`($4OXD$==sn(A zb%N`P6;!aZ_&tQK?9;oXK+>yK0Fb_4B8}Gc9=_qE z4vrtk$`XG9sc(+^zFU5DBBKCGmmU^S=(B+YI^R(0CzwTl!V~B+>6&A*-b3*vqUZ29 z>h*{0{GMtdJb5>+M|M-zqL1^jAs84C37n*)g1FM^LSl2uT{s^XsU1>k008(GZu0q0 zR1O?`ebi|LED~brtR*ec=-%uxo|LfWSiEG}Swe{Tg(i{4O^^jBL*VmWXSF1sbmQ|y zlW@ruXUw#y(=OatS2}(bEC=+uKT(4i2d#Zf5kCpnqAJUcda`GwW6=1HHE!`UVj4-b z(q_)8jUug!=6dbFu31c-6JXsmr92zfcQ_wdbN}r}!wxN_ZK)8AZIft;k+6IxdM}KW zyOS+@O}?7Cx<- zXT$Gi!zC3wS3=UY-#T!R18#S>JZ%LkA+TyITt@u4|nNuu% zZ@TSKWkZiu>Rmpl*;tgq89}&Ad_=utwYX301ni367jMcQ*#0gc-Wib_?z{&lonM25 z3O2^iQQP2BimXgT))XIVW6ve>4imKy@}q0evf0hNm3bYVjMD-MB1`RhYH;C-hv!uT zog_o!-OTMpX%g)Yy~{&YH(Nuyks1J?Yb!xVYI+Y_ZrGhNmOIL&nVI8g0=P|)^bOZ0 zU;1%bNC92w%FCCwLuIdYaMrR4Stj!JOvTt4xcIbS$PhJ}11|8>PP$RsjU|waZ}u1} zG#r!*iq5{J(|%lB+Vew4BpUETR;=P zvNbMg>N!4WtZwMVcg8 zZ$Fya<8!n*WM$qkdJkpOuAxXJl-lt_Ei;cw4LHMm05Da5vKf+a;M6t9m>NV=@}a6t zQ`%=zKtc+W+F+O9;xX89bcJXPMsr6=WRkN2F@07r79Br(#z_q|>Ie_ERDlRob6m3C zA1x8yDN1~JE?`s?t82YaCi@|Dg30iC4B7s=fobmRIqIi#GjeXSKMb=3F!d($LK3Wu z$57@fV1awCxZ4beo;qmL`r0`&cdiCr&?6Z%glWdlrdc%D%ndXXn$a_*zIp%v;GrX6 zk*I^-bJ-bX{Am*>axC&20C;j>1S|&t?u!2cCRnCX&AYYPH% zyS9^Af^NA%0VVGhy39JNfwt7ghw(`s031*Dg2y<1 z&u=M---$lxi$8v@jE!0V<)81f_ZNh$nWW|r#Ybbu<-?b*GzWlP#!FY_sR|Ey`=MSR zcSVj%P*fW2Lu065IRLy@TL%e$q+bRAKew!dENI7nIcNdo_iRvG5Gvpc%Gc`PXreY! zD#$+UvECP1Gr^*%4Eo_zQ2lMWM3DWS(oGQdT&>CG)&P)ws$;P+uBST26*c`xv)jWt z6;!b#3FGF;xCz#_PjxIa#z9g+8K-ySkjoN5_68oXg|U&sdizxWCkrzgyOy+A@Tw0k z5GQKg1lgZd1f_!N4`XaoL3g+R$H^)XkbUaiFHE@pEhcXqjO*cRl$1VZe;2l05frw+ zJtP*qamo4&B3OH49{2$4pZn~8iSqHzw#v_;?MQBdVlESxgABYeuhXX5T6NnxJ3HHT zp#)fy`{q3{?{8`2&&;VQLqhnb_&y&kD*Cv$mnFd`p(?u~gqqQj8UKot;p_1R#j%N? zzuh92=m^uA^!H-CJ0`I7n3*q7(TurO?NqL&Y`Q?ecLW?uxDeyvo$Kw^JobXpjW*}e zBH!wLUA7k~D2ol-wfCoM)de}$_QYMr`y=7yg9BHJ@vuyUb&P07V*`~Jdfb|Q7C$)_ zvnQl#ek_J8<%@(JepQ$clt*6s3m8ly>~6#bu?A(Y^ew99(prdDrCcK3SA9b!w$uXd8IcDKyGr#a*3qQwTd=Ocu4yGufEseW+Wf`d-ZL%P$8skrR^64UlFcWwIR! zO0R{WEN&RLH?Z2z5H+@i(}kV|A=YKt)S=GX__=TaJ#Zr{(l7^AT|t-38C0CU;HL<33mSXx(e4J6@FKz&;M2+aMqGygBpjQ@TZ&vJR)W z^9H`p!e346Z18!a+YKcho@s%dm~7d818>b@Hw%_Jo|XARZa%MCAt zLif#v6|+{%H{)B$CoMWlvWxroC6sa%TXf8o6damR4Xm@2CQ|$absyo!9Yq--TN4Qx z9iwfaX$-i(MHOPXI*Gmi^vb)II~;Dli$n*FAJGc%tC1+}^Nl!Wg|*>N*3M5R2tI)w zNUPcg2Ye@)Q4SQ=@JPef$CEF+A~yxV9=u(-X#HT~W-hX^eZ!uZr?<4XdXwXhosz&C zZj-B~erzn9-7Q%kU0PJ$Oo)u8^?Ea0jnYxe@;!J3^Kc=ECqaSf}A z6(bqfz6_&QpA+DxO$A)b%l85V(W(35F6Mkyv2FlGrkNMvqi!^-(DG)`kEjdH)$tQl z4^B=b-;Nv)9>MR9JmuNx-oW4daX>!E4yC%idlp%(LuE%M<}eX(3+JSb$8}AycHh0@M$&X^{JD;pA3$ulwS6|HP*tQ=_x3g}1gs<#!oP$##TG6 z&{0KD&Mi`obv~2HBwNRvpq{R6F|9lJKt;*hum8@crz1LI^3+&NP|p#KQc{4z`bzCD zYKPWFs(Bx4P77!zIqb(J9N4Z_S^IkTyPHiY^m7XRRcs?Iy?bSz&Z&ipIP$YM9qDbw z>Pm4nxFtEuMfNx8?6e8WN z=Fk-RVWr%?Vm0~gH>^k*kHxnq&cxP?(ilzc z)Yu;1Y_a}QmsJ@V9R!39#SwOKY$(#qn1dJ?9*Qhi?+>K*G>n$tW~M7)QHsmNIpL?i_17|5x%+P$Z3|311l^`Ws$!|xlv); zgZrP>D|(NN_;Cm(T+FS;?iF{D7NH)Z-5(w>oF*tf-o3QOpCKyvI|&GStC3 zU6A7qVjF(+I(CB5+H)wVb6@DauL-5~+4;6S{qHL;ALtN1E>6Le8JTE$R%|pM&1xaP zlw@OBE^7))O(^5SvFm^jeA>ADHpVUF+UYooG#<< z_VhgRp28EK*ZV!R^v=dQgnN=*GY_#}2*qMgsgeuJyt`{9=)S_2$UW~8C6?`X7-`$> zs(2;XLs^%zkWu!2OBmUPjoxKCRWWOKVg^YhmN1+~GA4AfZLeEUoG#Kool^g^^y@Eq zawzKgw1usK-Txysh<)lm#3$h=%=9QUK6ZC{j*We)r4>{gmwo}PZ69W?n0NCU>}K7z z6XrAaQ@2zhTZ^Yn5+7T~hxQt4nK>!6K$e3rWp31^ApBGC<`ozaF1?`R!k~&OWb1dh zB`q$3&A6EzRHbOK|FlUW3`)?2<{T$E_+fho*&xli z*qlZ_`ZaqQ#y0G_vjNV>rpn#!UF2<@cMY6w@tR>3xqS3i#{tr$yagZ6ltCN`kOW>_{p?R6W=)!VUr7k1;X0+ezPMO zF*`vnaZn}OU5iT0@%D0Sc2H36AMT8l)NZ_(??a9;>LOB73Xw5XZuxj&<G{*oHWM&vZVDVJF^yy8Q1;YJ$p43Y89`5g7h z6U!Gki^aU(uwCemis$RB)%W%|@ok4ACG?bJ**Q0@A?%i(8#$z&B_<(gg2|gw!ibo| z7@Aydh#DX?@eZ|exs9F4*V+ZVVmkPAXRau)qfxo!LYvg>IC z(Qa6?ndQuIv{pB%jaSFy*%?vL*(L5fc7{B}3!~XIqM(G(#-ZdWYgEN#z2va=e{lpo z+&zMMg3V8?6?ra)jr-Dqw_+c*a5YSK;9h3-^|-I!J$mfw$?Q$8&nH*wOTMMt>@h5g zW@pt-grvOdk;{*aZx5M|6Gwfj$YUY4U^#e}*~?{CJW=saS;n%S$sNp@)6C(RA+Z2A z)MuPTcXgV7-w-|XU`W8d57sub)7=1xq?94;;|x!Y-Epu9Tj|fg$9;UXtTU=Xv?ebD z{IPofRr(kwplOTO2FKAYzBFn?Oc3=Th5gb;ezaHWA2OJb0~4KOciHquj(>WU6D?6Y zE+#AXb8qJm+Gqi?iN})Ix~b3RdtoU#490T$nR@HOCLgxa7oQ2kxRr{xac)P}+#=5H zeUa9< zwqQ&DMCRb+%mxTTTzW5B6`nTSWcAuvf?|l&P&0MCzr-3H!@i54de>pfpd2{?lf{v$758|xm;*$z)+A=t!1s(DEL37-ey}lmjnVGwSS)^&S@`X;mJ^8tYV|+%`qtjy46Vvo+ z>v~RH?m__nYp5g_1R5K$PX+NP?^pWiEgYunwQv^~PQm?@2Ld=A3&F^ zH!GA|ad7Jb<0x%hov$in)8;Qkl`*zT1n|qZ3nCuKT@DI!S6F}bhXuJswpuq|?fAd9 zRUYN@Oo_RL1uHhjGbg$*+N4b=bc%#+X7P!YX3&}MCg+EVyJQy*Ss7IQj*C&>p6)D? z_Fze+Q+l`0L{I#)HLGt^R;yw5Ui+PZ8b6`hhN~+p^ln_KE3YUd^`w@DEavYF+H-z&~0Yhl5mH1@swov~uCMOvy!uOW0@TEwGM5nD#9$*kGC&D7}bofoh3 zue7lKpaL@5KF)AYJEmAtr+=sE40$83!!Hv-DTGg5W6-+hH=eDZ&2M2KGLWYd z$y2? zq|2{?nXUri>?$ujn-)OHsG8LrKj_zo*g8-r$n5wrP>P7`+?LwHSog!B-ypJKMV_dj zDJC);wrOR^oJod2_4eAsf|90V>D<^V7`%U~Tkn8Va9JTeaQnVW%cxSObYP;{p3HiE z#24OgJt!`z$hBfp;>8p5pTi}hB5Ji0d0o0XQvGD%uTim62Y1h8ES+H2=Y-@x>A7sX z$K)!wTC>Q8n56lR+c;W|4lNAm76q0d!-w>|=?LctUh%`e-TX-;OURuL6p7kMjz+WB zI(%z?wQ~JKxqNPundZunxyjRYA3>0op-^hXmW1|Z?)-a!#&u%VKYE&?oKhs(pak-c1Xk zB83Nj%?N2jOIk37K|JbBlDwbzrsu9q3jfMTY;Y+zUEruO`5`0I&_$PxchICDoV#k@ zWTVb{+4RiBxw1O&0%044GbC(&)^l?H&L6}{mCKJBacq1ph`u&uGWRjo!`$!&TL*4y z4gY;*^Nxw7rX-i|l#rNYteDnrmM=VmTYA{MT{M4!mPd_I8kO{iui$TZ_?znWadpMN zvi|2Kvp@R$f0{S*p*^4k)vx*d-&jcew;dcfIwY9wMM`SFwpuZlHS;1y&}U%s{3`Fj%q z{M@9!va~bLEj6fVp*Y`9+Bwqj!Lxj4u!xq=EEAs=vsy$FYAjUQuXxDNH2C- z&vcqd^2ai#1U%M84L2<-vToEk8KI{UJ{d@6*6xztVMv)$9wPd#V&2Q%O^SKS&&-aN z&DVVEtj(go9aDzxp_~sp;Q^n*X4FRyN4UqA?rcn+<>!u?3!@&5c2NZu)*Hz?a2lPW zX!3L%{o5_V4^7Fe>)|}lKqD!HftDC~^ssK1C+aOK=I3eKFt&TRTw;8r-79;LW)kNy z%d`x=_bwYb`=c@5te!D6I}SlBSUicuxdR!c)}0xWcXE{wfa1| z1LyoI%`7TSXu-NJcKL{ZPo9jBOi#wy%yjgrWYid5H}w80x~($| zW6Om9)d#O0$R`dyv)=a@rQJ#L)5cvqxO6=L+}ZHgID`Eo=sZ^kj{Q=tp@JR6wk2?e zt(HQ9Ql3dj4^FMF7@D6iU;l_k?%944ojN148r(w7=)fnddD zGPV;rx+{Sp3*1mpgg^2&L^4oN>#O;d{0hZW(T)gut4&8n7(BK}@h`=Y~wOC8+!i z_VJ8N0kfcCmZ<5+kF8|{quX_-{3j4pv{2Z13^RR%!%lNGzv@YT&KKvX#U*!Z1|~94 z!q6xAUokoMLX-V{hZG3vz3(Y4*p-g)EEZHMB z_+|a`9+GK#vm=jpqX-%`X?)7rO?YTy4ay^TkWBK<%GjBNZQTj&Idu=~@G++usR~)M zS1e&^#$EO^H9o7yM0nyG1{Me=nNB2OmD}&$m&@N}dR1=pnL>L&~ z(0VCu1PmSCyFqEjnNtY;F^+@hOmvevR_Nv(kyM)PAbFAkftT%jZ4!&<8_eZsGhd5ngQ%B1Q&m6w3>Q@+Bv)k-@elc0z;@2wE zdhjU6Ul$uJWj-G59iED*>?SjIHl49Lx=ox)f_Z*Cj*1UC%SsIEQOrw-{fTjoMV`ui zEHJlWuE&bU&7f{y0c(0!hi+Q;C1S-5Ps zD7mDDdojXFwv;OtvkNKKFY=lz#MU8DVioJgKt0W0tWGbi=**3+rsdku$9G4~Z1b=- zQD|YsI55tIdXDnqe6u5{GYpUZP#LX##Bwn**6RjY`li=z$OHF3^!gNP_%%72+AFv` z0{1^ufjnAhStVu>S>{|r_fL;23>7LpbJnzL}ru%DFrpk*y z+?q-Br>7)>6_wN>AwAXKnc@6$O&v{{^}dU4F&Lo=8Jz!{42<{_Pt0xT`4j!kEouhN zizQGVCF9GTOap7G+Rtj{vU@Xhc3`1s)oa|+LUZo#3^$`!=ruQRr{+-Hfy5oSlxepV z_P&9r36_slj`bQwd?#&Y^5&urx~WxWaP^-v+7Knz-JF>Kw-7=@ zcA9t$4G%f-+{kP{c{{E$&V><@8EP?$Fw`XRETpqs4twYqTHaK`S*EAdVdb*ErU1#t zBc}Gox`h(@M7!?jm&=%)^9pSxXz3l6Jl*;oNVl$Cz=;VJdPE9)f-OR;`T|8+Q8QL| zTj#sX%yh*qm6}&7;?id$OQ9CxSPyRCh zxwyMIVrLaxb~x8HJie-F*pTPn&1$nhV-(G=uB0jnDw|fE88Kv}qnc)eN=fzg6yc_A zmS-FMn0A==?}np6Lhr6Ej`1Bon4-f6$o*a7_Iif8Y*tg&R6TBXnT02Q%_n20V()jG zw5wg~yU)I(_H1-&y0_a5mu8mS`J#)QeUO94-efj)6!CaXMbIc;3t^WiYU*TY+}(L& zuaDSr^x5#AJe%y<-?vO6J*w#@l%5WM%_(jp1oQsgLHe@CYyz3hLhdAHoE>;8%72Wg z;pVaQJWfy$h3_WeNG0W?i^?!I`!rQdmN)qo*v}f}vZMMP^!1xFYQzW@1iHr}e2hLf zhH_741QfAM{izZp>&2HnO@x%#x7LRbVcxeXTcRCVZ{mkJcCC{qJyT@dE^&%%&}@9C zjrZ3)?h@UR3+HLL2_Fv&rUc)VhsTuf%j^g?>>)AsvCewf7{@nGmxOUdh>Rjib5JQ% zI0KtCG-3DUQ%cRhL7kHcy@)cGF=pY1(X?u`e!nC(_<-(9KUGM# z)zCmRubjogHzKEaWw#R7LEn!t){exTB*`*iVe#T3|+rJSwjKZTt$H@p#I9RiQ> z$vvDZ=>H(iu!NLjFL6YDYl$=9u>&e_1GTpipFJhGS7lM_s7Aal4@ z*mK%lH%8)*>2fsY4+QNAkvW~CBIEjVuyUiO(BV0!VP3GFB-@~{w0PIV9G0vuZ@Ike^q|yRIPGAQ1GxK$e>3-O|}U5X)rCT9Vy|~1#mA~ZG%0DXW|H3yy|DWx@N!#qtyf*w>ILQmq6)pSJ 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 literal 9842 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 - QString user = autoTester->getSelectedUser(); - - textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/branchUtils.js\";" << endl; - textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; - textStream << "var autoTester = createAutoTester(Script.resolvePath(\".\"));" << endl << endl; - - textStream << "var testsRootPath = autoTester.getTestsRootPath();" << endl << endl; - - // Wait 10 seconds before starting - textStream << "if (typeof Test !== 'undefined') {" << endl; - textStream << " Test.wait(10000);" << endl; - textStream << "};" << endl << endl; - - textStream << "autoTester.enableRecursive();" << endl; - textStream << "autoTester.enableAuto();" << endl << endl; - - // This is used to verify that the recursive test contains at least one test - bool testFound{ false }; - - // Directories are included in reverse order. The autoTester scripts use a stack mechanism, - // so this ensures that the tests run in alphabetical order (a convenience when debugging) - QStringList directories; - - // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - - QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir(directory); - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname { directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - } - - if (interactiveMode && !testFound) { - QMessageBox::information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); - allTestsFilename.close(); - return; - } - - // Now include the test scripts - for (int i = 0; i < directories.length(); ++i) { - includeTest(textStream, directories.at(i)); - } - - textStream << endl; - textStream << "autoTester.runRecursive();" << endl; - - allTestsFilename.close(); - - if (interactiveMode) { - QMessageBox::information(0, "Success", "Script has been created"); - } -} - void Test::createTests() { // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on // Any existing expected result images will be deleted @@ -613,9 +445,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { return relevantTextFromTest; } -// Create an MD file for a user-selected test. -// The folder selected must contain a script named "test.js", the file produced is named "test.md" -void Test::createMDFile() { +void Test::createFileSetup() { // Folder selection QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -624,20 +454,15 @@ void Test::createMDFile() { } _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, - QFileDialog::ShowDirsOnly); + QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return if (_testDirectory == "") { _testDirectory = previousSelection; return; } - - createMDFile(_testDirectory); - - QMessageBox::information(0, "Success", "MD file has been created"); } - -void Test::createAllMDFiles() { +void Test::createAllFilesSetup() { // Select folder to start recursing from QString previousSelection = _testsRootDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -646,13 +471,25 @@ void Test::createAllMDFiles() { } _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent, - QFileDialog::ShowDirsOnly); + QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return if (_testsRootDirectory == "") { _testsRootDirectory = previousSelection; return; } +} + +// Create an MD file for a user-selected test. +// The folder selected must contain a script named "test.js", the file produced is named "test.md" +void Test::createMDFile() { + createFileSetup(); + createMDFile(_testDirectory); + QMessageBox::information(0, "Success", "MD file has been created"); +} + +void Test::createAllMDFiles() { + createAllFilesSetup(); // First test if top-level folder has a test.js file const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; @@ -681,9 +518,9 @@ void Test::createAllMDFiles() { QMessageBox::information(0, "Success", "MD files have been created"); } -void Test::createMDFile(const QString& _testDirectory) { +void Test::createMDFile(const QString& directory) { // Verify folder contains test.js file - QString testFileName(_testDirectory + "/" + TEST_FILENAME); + QString testFileName(directory + "/" + TEST_FILENAME); QFileInfo testFileInfo(testFileName); if (!testFileInfo.exists()) { QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); @@ -692,7 +529,7 @@ void Test::createMDFile(const QString& _testDirectory) { ExtractedText testScriptLines = getTestScriptLines(testFileName); - QString mdFilename(_testDirectory + "/" + "test.md"); + QString mdFilename(directory + "/" + "test.md"); QFile mdFile(mdFilename); if (!mdFile.open(QIODevice::WriteOnly)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); @@ -729,6 +566,204 @@ void Test::createMDFile(const QString& _testDirectory) { mdFile.close(); } +void Test::createTestAutoScript() { + createFileSetup(); + createTestAutoScript(_testDirectory); + QMessageBox::information(0, "Success", "'autoTester.js` script has been created"); +} + +void Test::createAllTestAutoScripts() { + createAllFilesSetup(); + + // First test if top-level folder has a test.js file + const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createTestAutoScript(_testsRootDirectory); + } + + QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createTestAutoScript(directory); + } + } + + QMessageBox::information(0, "Success", "'autoTester.js' scripts have been created"); +} + +void Test::createTestAutoScript(const QString& directory) { + // Verify folder contains test.js file + QString testFileName(directory + "/" + TEST_FILENAME); + QFileInfo testFileInfo(testFileName); + if (!testFileInfo.exists()) { + QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); + return; + } + + QString testAutoScriptFilename(directory + "/" + "testAuto.js"); + QFile testAutoScriptFile(testAutoScriptFilename); + if (!testAutoScriptFile.open(QIODevice::WriteOnly)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create file " + testAutoScriptFilename); + exit(-1); + } + + QTextStream stream(&testAutoScriptFile); + + stream << "if (typeof PATH_TO_THE_REPO_PATH_UTILS_FILE === 'undefined') PATH_TO_THE_REPO_PATH_UTILS_FILE = 'https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js';\n"; + stream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);\n"; + stream << "var autoTester = createAutoTester(Script.resolvePath('.'));\n\n"; + stream << "autoTester.enableAuto();\n\n"; + stream << "Script.include('./test.js?raw=true');\n"; + + testAutoScriptFile.close(); +} + +// Creates a single script in a user-selected folder. +// This script will run all text.js scripts in every applicable sub-folder +void Test::createRecursiveScript() { + createFileSetup(); + createRecursiveScript(_testDirectory, true); + QMessageBox::information(0, "Success", "'testRecursive.js` script has been created"); +} + +// This method creates a `testRecursive.js` script in every sub-folder. +void Test::createAllRecursiveScripts() { + createAllFilesSetup(); + + createRecursiveScript(_testsRootDirectory, false); + + QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + // Only process directories that have sub-directories + bool hasNoSubDirectories{ true }; + QDirIterator it2(directory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it2.hasNext()) { + QString directory2 = it2.next(); + + // Only process directories + QDir dir; + if (isAValidDirectory(directory2)) { + hasNoSubDirectories = false; + break; + } + } + + if (!hasNoSubDirectories) { + createRecursiveScript(directory, false); + } + } + + QMessageBox::information(0, "Success", "Scripts have been created"); +} + +void Test::createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode) { + const QString recursiveTestsFilename("testRecursive.js"); + QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename); + if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\""); + + exit(-1); + } + + QTextStream textStream(&allTestsFilename); + + textStream << "// This is an automatically generated file, created by auto-tester" << endl; + + // Include 'autoTest.js' + QString branch = autoTester->getSelectedBranch(); + QString user = autoTester->getSelectedUser(); + + textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + + "/tests/utils/branchUtils.js\";" + << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; + textStream << "var autoTester = createAutoTester(Script.resolvePath(\".\"));" << endl << endl; + + textStream << "var testsRootPath = autoTester.getTestsRootPath();" << endl << endl; + + // Wait 10 seconds before starting + textStream << "if (typeof Test !== 'undefined') {" << endl; + textStream << " Test.wait(10000);" << endl; + textStream << "};" << endl << endl; + + textStream << "autoTester.enableRecursive();" << endl; + textStream << "autoTester.enableAuto();" << endl << endl; + + // This is used to verify that the recursive test contains at least one test + bool testFound{ false }; + + // Directories are included in reverse order. The autoTester scripts use a stack mechanism, + // so this ensures that the tests run in alphabetical order (a convenience when debugging) + QStringList directories; + + // First test if top-level folder has a test.js file + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + directories.push_front(testPathname); + + testFound = true; + } + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir(directory); + if (!isAValidDirectory(directory)) { + continue; + } + + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + directories.push_front(testPathname); + + testFound = true; + } + } + + if (interactiveMode && !testFound) { + QMessageBox::information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); + allTestsFilename.close(); + return; + } + + // Now include the test scripts + for (int i = 0; i < directories.length(); ++i) { + includeTest(textStream, directories.at(i)); + } + + textStream << endl; + textStream << "autoTester.runRecursive();" << endl; + + allTestsFilename.close(); +} + void Test::createTestsOutline() { QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 853e9c98e2..57e6ccd90f 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -46,22 +46,29 @@ public: void startTestsEvaluation(const QString& testFolder = QString(), const QString& branchFromCommandLine = QString(), const QString& userFromCommandLine = QString()); void finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar); - void createRecursiveScript(); - void createAllRecursiveScripts(); - void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); - void createTests(); void createTestsOutline(); + void createFileSetup(); + void createAllFilesSetup(); + void createMDFile(); void createAllMDFiles(); - void createMDFile(const QString& topLevelDirectory); + void createMDFile(const QString& directory); + + void createTestAutoScript(); + void createAllTestAutoScripts(); + void createTestAutoScript(const QString& directory); void createTestRailTestCases(); void createTestRailRun(); void updateTestRailRunResult(); + void createRecursiveScript(); + void createAllRecursiveScripts(); + void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); + bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 13bda4853f..9e8aa406b8 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -78,6 +78,14 @@ void AutoTester::on_createAllMDFilesButton_clicked() { _test->createAllMDFiles(); } +void AutoTester::on_createTestAutoScriptButton_clicked() { + _test->createTestAutoScript(); +} + +void AutoTester::on_createAllTestAutoScriptsButton_clicked() { + _test->createAllTestAutoScripts(); +} + void AutoTester::on_createTestsOutlineButton_clicked() { _test->createTestsOutline(); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index e29da5b716..26eec6f07f 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -50,6 +50,9 @@ private slots: void on_createMDFileButton_clicked(); void on_createAllMDFilesButton_clicked(); + void on_createTestAutoScriptButton_clicked(); + void on_createAllTestAutoScriptsButton_clicked(); + void on_createTestsOutlineButton_clicked(); void on_createTestRailTestCasesButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index ac8fcf5e86..576ad14aae 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,7 +6,7 @@ 0 0 - 432 + 582 734 @@ -23,8 +23,8 @@ - 166 - 610 + 235 + 620 100 40 @@ -36,9 +36,9 @@ - 12 + 30 140 - 408 + 521 461 @@ -52,7 +52,7 @@ - 96 + 145 20 220 40 @@ -65,8 +65,8 @@ - 96 - 100 + 20 + 140 220 40 @@ -78,8 +78,8 @@ - 96 - 150 + 270 + 140 220 40 @@ -91,8 +91,8 @@ - 96 - 230 + 145 + 80 220 40 @@ -104,8 +104,8 @@ - 96 - 310 + 20 + 260 220 40 @@ -117,8 +117,8 @@ - 96 - 360 + 270 + 260 220 40 @@ -127,6 +127,32 @@ Create all Recursive Scripts + + + + 20 + 200 + 220 + 40 + + + + Create testAuto script + + + + + + 270 + 200 + 220 + 40 + + + + Create all testAuto scripts + + @@ -135,8 +161,8 @@ - 90 - 100 + 150 + 230 255 23 @@ -148,8 +174,8 @@ - 90 - 50 + 150 + 180 131 20 @@ -164,8 +190,8 @@ - 200 - 40 + 260 + 170 101 40 @@ -182,8 +208,8 @@ - 180 - 160 + 210 + 230 161 40 @@ -195,8 +221,8 @@ - 80 - 40 + 110 + 110 95 20 @@ -211,8 +237,8 @@ - 180 - 100 + 210 + 170 161 40 @@ -224,8 +250,8 @@ - 180 - 40 + 210 + 110 161 40 @@ -237,8 +263,8 @@ - 80 - 60 + 110 + 130 95 20 @@ -255,8 +281,8 @@ - 100 - 100 + 160 + 130 211 40 @@ -268,8 +294,8 @@ - 100 - 170 + 160 + 200 211 40 @@ -283,8 +309,8 @@ - 110 - 90 + 160 + 80 81 16 @@ -301,8 +327,8 @@ - 200 - 85 + 250 + 75 140 24 @@ -311,8 +337,8 @@ - 200 - 47 + 250 + 37 140 24 @@ -321,8 +347,8 @@ - 110 - 50 + 160 + 40 81 16 @@ -342,7 +368,7 @@ 0 0 - 432 + 582 21 From de9be96acd62e732baa3d28f9867bfe4afd9d412 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 20 Aug 2018 08:36:10 -0700 Subject: [PATCH 02/90] Deal correctly with missing test.js --- tools/auto-tester/src/Test.cpp | 22 ++++++++++++++-------- tools/auto-tester/src/Test.h | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index ee4dc5fc88..49ea3172e0 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -484,8 +484,10 @@ void Test::createAllFilesSetup() { // The folder selected must contain a script named "test.js", the file produced is named "test.md" void Test::createMDFile() { createFileSetup(); - createMDFile(_testDirectory); - QMessageBox::information(0, "Success", "MD file has been created"); + + if (createMDFile(_testDirectory)) { + QMessageBox::information(0, "Success", "MD file has been created"); + } } void Test::createAllMDFiles() { @@ -518,13 +520,13 @@ void Test::createAllMDFiles() { QMessageBox::information(0, "Success", "MD files have been created"); } -void Test::createMDFile(const QString& directory) { +bool Test::createMDFile(const QString& directory) { // Verify folder contains test.js file QString testFileName(directory + "/" + TEST_FILENAME); QFileInfo testFileInfo(testFileName); if (!testFileInfo.exists()) { QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); - return; + return false; } ExtractedText testScriptLines = getTestScriptLines(testFileName); @@ -564,12 +566,15 @@ void Test::createMDFile(const QString& directory) { } mdFile.close(); + return true; } void Test::createTestAutoScript() { createFileSetup(); - createTestAutoScript(_testDirectory); - QMessageBox::information(0, "Success", "'autoTester.js` script has been created"); + + if (createTestAutoScript(_testDirectory)) { + QMessageBox::information(0, "Success", "'autoTester.js` script has been created"); + } } void Test::createAllTestAutoScripts() { @@ -602,13 +607,13 @@ void Test::createAllTestAutoScripts() { QMessageBox::information(0, "Success", "'autoTester.js' scripts have been created"); } -void Test::createTestAutoScript(const QString& directory) { +bool Test::createTestAutoScript(const QString& directory) { // Verify folder contains test.js file QString testFileName(directory + "/" + TEST_FILENAME); QFileInfo testFileInfo(testFileName); if (!testFileInfo.exists()) { QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); - return; + return false; } QString testAutoScriptFilename(directory + "/" + "testAuto.js"); @@ -628,6 +633,7 @@ void Test::createTestAutoScript(const QString& directory) { stream << "Script.include('./test.js?raw=true');\n"; testAutoScriptFile.close(); + return true; } // Creates a single script in a user-selected folder. diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 57e6ccd90f..09363b17ee 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -55,11 +55,11 @@ public: void createMDFile(); void createAllMDFiles(); - void createMDFile(const QString& directory); + bool createMDFile(const QString& directory); void createTestAutoScript(); void createAllTestAutoScripts(); - void createTestAutoScript(const QString& directory); + bool createTestAutoScript(const QString& directory); void createTestRailTestCases(); void createTestRailRun(); From 91aa568832e20db4b600db47aba2ba1ad5bcb98a Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 20 Aug 2018 12:08:33 -0700 Subject: [PATCH 03/90] Improving error handling. --- tools/auto-tester/src/Test.cpp | 33 ++++++++++++++++++++++++--------- tools/auto-tester/src/Test.h | 4 ++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 49ea3172e0..3ddb9c0550 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -445,7 +445,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { return relevantTextFromTest; } -void Test::createFileSetup() { +bool Test::createFileSetup() { // Folder selection QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -459,10 +459,13 @@ void Test::createFileSetup() { // If user cancelled then restore previous selection and return if (_testDirectory == "") { _testDirectory = previousSelection; - return; + return false; } + + return true; } -void Test::createAllFilesSetup() { + +bool Test::createAllFilesSetup() { // Select folder to start recursing from QString previousSelection = _testsRootDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -476,14 +479,18 @@ void Test::createAllFilesSetup() { // If user cancelled then restore previous selection and return if (_testsRootDirectory == "") { _testsRootDirectory = previousSelection; - return; + return false; } + + return true; } // Create an MD file for a user-selected test. // The folder selected must contain a script named "test.js", the file produced is named "test.md" void Test::createMDFile() { - createFileSetup(); + if (!createFileSetup()) { + return; + } if (createMDFile(_testDirectory)) { QMessageBox::information(0, "Success", "MD file has been created"); @@ -491,7 +498,9 @@ void Test::createMDFile() { } void Test::createAllMDFiles() { - createAllFilesSetup(); + if (!createAllFilesSetup()) { + return; + } // First test if top-level folder has a test.js file const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; @@ -570,7 +579,9 @@ bool Test::createMDFile(const QString& directory) { } void Test::createTestAutoScript() { - createFileSetup(); + if (!createFileSetup()) { + return; + } if (createTestAutoScript(_testDirectory)) { QMessageBox::information(0, "Success", "'autoTester.js` script has been created"); @@ -578,7 +589,9 @@ void Test::createTestAutoScript() { } void Test::createAllTestAutoScripts() { - createAllFilesSetup(); + if (!createAllFilesSetup()) { + return; + } // First test if top-level folder has a test.js file const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; @@ -646,7 +659,9 @@ void Test::createRecursiveScript() { // This method creates a `testRecursive.js` script in every sub-folder. void Test::createAllRecursiveScripts() { - createAllFilesSetup(); + if (!createAllFilesSetup()) { + return; + } createRecursiveScript(_testsRootDirectory, false); diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 09363b17ee..1984ede234 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -50,8 +50,8 @@ public: void createTestsOutline(); - void createFileSetup(); - void createAllFilesSetup(); + bool createFileSetup(); + bool createAllFilesSetup(); void createMDFile(); void createAllMDFiles(); From e15badad18791e093c94b9cea6e2c38fc9bbf5e7 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 20 Aug 2018 12:53:37 -0700 Subject: [PATCH 04/90] Improving error handling. --- tools/auto-tester/src/Test.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 3ddb9c0550..45344c1716 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -652,7 +652,10 @@ bool Test::createTestAutoScript(const QString& directory) { // Creates a single script in a user-selected folder. // This script will run all text.js scripts in every applicable sub-folder void Test::createRecursiveScript() { - createFileSetup(); + if (!createFileSetup()) { + return; + } + createRecursiveScript(_testDirectory, true); QMessageBox::information(0, "Success", "'testRecursive.js` script has been created"); } From 96deeac126d8128bffa795ac3c677b9afafb2234 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 20 Aug 2018 14:26:37 -0700 Subject: [PATCH 05/90] Give final evaluation message also in non-interactive mode. --- tools/auto-tester/src/Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 45344c1716..b7fe3a4f96 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -244,7 +244,7 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) { bool success = compareImageLists((!isRunningFromCommandline && interactiveMode), progressBar); - if (interactiveMode && !isRunningFromCommandline) { + if (!isRunningFromCommandline) { if (success) { QMessageBox::information(0, "Success", "All images are as expected"); } else { From ff4d37dd2fcab72b3ce89cb946e835dd8690fb59 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 20 Aug 2018 16:44:08 -0700 Subject: [PATCH 06/90] Updated to the new TestRail project. --- tools/auto-tester/src/TestRailInterface.cpp | 8 ++++---- tools/auto-tester/src/TestRailInterface.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 93559490e5..5c950e0817 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -25,7 +25,7 @@ TestRailInterface::TestRailInterface() { _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); ////_testRailTestCasesSelectorWindow.setUser("nissim.hadar@gmail.com"); - _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); ////_testRailTestCasesSelectorWindow.setProjectID(2); _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); @@ -36,7 +36,7 @@ TestRailInterface::TestRailInterface() { _testRailRunSelectorWindow.setUser("@highfidelity.io"); ////_testRailRunSelectorWindow.setUser("nissim.hadar@gmail.com"); - _testRailRunSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + _testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); ////_testRailRunSelectorWindow.setProjectID(2); _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); @@ -47,7 +47,7 @@ TestRailInterface::TestRailInterface() { _testRailResultsSelectorWindow.setUser("@highfidelity.io"); ////_testRailResultsSelectorWindow.setUser("nissim.hadar@gmail.com"); - _testRailResultsSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); ////_testRailResultsSelectorWindow.setProjectID(2); _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); @@ -908,7 +908,7 @@ QDomElement TestRailInterface::processTestXML(const QString& fullDirectory, ++i; QString title{ words[i] }; for (++i; i < words.length() - 1; ++i) { - title += " / " + words[i]; + title += "/" + words[i]; } QDomElement titleElement = _document.createElement("title"); diff --git a/tools/auto-tester/src/TestRailInterface.h b/tools/auto-tester/src/TestRailInterface.h index 6f250dfbba..ba5e94957b 100644 --- a/tools/auto-tester/src/TestRailInterface.h +++ b/tools/auto-tester/src/TestRailInterface.h @@ -92,10 +92,10 @@ public: private: // HighFidelity Interface project ID in TestRail - const int INTERFACE_PROJECT_ID{ 24 }; + const int INTERFACE_AUTOMATION_PROJECT_ID{ 26 }; // Rendering suite ID - const int INTERFACE_SUITE_ID{ 1147 }; + const int INTERFACE_SUITE_ID{ 1312 }; QDomDocument _document; From 4e677c85f6f42f4ed7739a44e0c1b9b17fee5c42 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 31 Aug 2018 23:13:50 -0700 Subject: [PATCH 07/90] Can rename the High Fidelity folder in AppData --- tools/auto-tester/src/Test.cpp | 32 +++++++++++++++++++++ tools/auto-tester/src/Test.h | 3 ++ tools/auto-tester/src/TestRailInterface.cpp | 12 -------- tools/auto-tester/src/ui/AutoTester.cpp | 4 +++ tools/auto-tester/src/ui/AutoTester.h | 3 ++ tools/auto-tester/src/ui/AutoTester.ui | 20 ++++++++++++- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index b7fe3a4f96..80cadc2bb8 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -920,6 +920,38 @@ void Test::createTestRailRun() { _testRailInterface.createTestRailRun(outputDirectory); } +void Test::runNow() { + // Rename the existing data directory, and create an empty one + QString dataDirectory{ "NOT FOUND" }; + +#ifdef Q_OS_WIN + dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; +#endif + + QDir highfidelityDirectory{ dataDirectory + "\\High Fidelity" }; + + if (!highfidelityDirectory.exists()) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "The High Fidelity data folder was not found in " + dataDirectory); + exit(-1); + } + + // The original folder is saved in a unique name + QDir savedDataFolder{ dataDirectory + "/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + highfidelityDirectory.rename(QDir::fromNativeSeparators(highfidelityDirectory.path()), + QDir::toNativeSeparators(savedDataFolder.path())); + + QDir().mkdir(highfidelityDirectory.path()); + + //////////////////////////////////////////////////////////////////////////////// + + // Finally - restore the data folder + QDir().rmdir(highfidelityDirectory.path()); + + highfidelityDirectory.rename(QDir::fromNativeSeparators(savedDataFolder.path()), + QDir::toNativeSeparators(highfidelityDirectory.path())); +} + void Test::updateTestRailRunResult() { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, "Zipped Test Results (*.zip)"); diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 1984ede234..5019d91345 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -63,6 +63,9 @@ public: void createTestRailTestCases(); void createTestRailRun(); + + void runNow(); + void updateTestRailRunResult(); void createRecursiveScript(); diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 5c950e0817..9678c52e13 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -21,37 +21,25 @@ TestRailInterface::TestRailInterface() { _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net"); - ////_testRailTestCasesSelectorWindow.setURL("https://nissimhadar.testrail.io"); _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); - ////_testRailTestCasesSelectorWindow.setUser("nissim.hadar@gmail.com"); _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); - ////_testRailTestCasesSelectorWindow.setProjectID(2); _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); - ////_testRailTestCasesSelectorWindow.setSuiteID(2); _testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net"); - ////_testRailRunSelectorWindow.setURL("https://nissimhadar.testrail.io"); _testRailRunSelectorWindow.setUser("@highfidelity.io"); - ////_testRailRunSelectorWindow.setUser("nissim.hadar@gmail.com"); _testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); - ////_testRailRunSelectorWindow.setProjectID(2); _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); - ////_testRailRunSelectorWindow.setSuiteID(2); _testRailResultsSelectorWindow.setURL("https://highfidelity.testrail.net"); - ////_testRailResultsSelectorWindow.setURL("https://nissimhadar.testrail.io"); _testRailResultsSelectorWindow.setUser("@highfidelity.io"); - ////_testRailResultsSelectorWindow.setUser("nissim.hadar@gmail.com"); _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); - ////_testRailResultsSelectorWindow.setProjectID(2); _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); - ////_testRailResultsSelectorWindow.setSuiteID(2); } QString TestRailInterface::getObject(const QString& path) { diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 9e8aa406b8..f4b7f22bec 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -98,6 +98,10 @@ void AutoTester::on_createTestRailRunButton_clicked() { _test->createTestRailRun(); } +void AutoTester::on_runNowButton_clicked() { + _test->runNow(); +} + void AutoTester::on_updateTestRailRunResultsButton_clicked() { _test->updateTestRailRunResult(); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 26eec6f07f..f0b5a99bf8 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -57,6 +57,9 @@ private slots: void on_createTestRailTestCasesButton_clicked(); void on_createTestRailRunButton_clicked(); + + void on_runNowButton_clicked(); + void on_updateTestRailRunResultsButton_clicked(); void on_hideTaskbarButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 576ad14aae..ca14e417c9 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -43,7 +43,7 @@ - 0 + 1 @@ -154,6 +154,24 @@ + + + Run + + + + + 200 + 200 + 93 + 28 + + + + Run now + + + Evaluate From fb80e9c3953f7032b74fc53cd087b9f9c989214f Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 1 Sep 2018 19:48:43 -0700 Subject: [PATCH 08/90] Added new class. --- tools/auto-tester/src/Test.cpp | 30 +--------------- tools/auto-tester/src/Test.h | 3 ++ tools/auto-tester/src/TestRunner.cpp | 51 ++++++++++++++++++++++++++++ tools/auto-tester/src/TestRunner.h | 32 +++++++++++++++++ 4 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 tools/auto-tester/src/TestRunner.cpp create mode 100644 tools/auto-tester/src/TestRunner.h diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 80cadc2bb8..16f06e20e7 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -921,35 +921,7 @@ void Test::createTestRailRun() { } void Test::runNow() { - // Rename the existing data directory, and create an empty one - QString dataDirectory{ "NOT FOUND" }; - -#ifdef Q_OS_WIN - dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; -#endif - - QDir highfidelityDirectory{ dataDirectory + "\\High Fidelity" }; - - if (!highfidelityDirectory.exists()) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "The High Fidelity data folder was not found in " + dataDirectory); - exit(-1); - } - - // The original folder is saved in a unique name - QDir savedDataFolder{ dataDirectory + "/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; - highfidelityDirectory.rename(QDir::fromNativeSeparators(highfidelityDirectory.path()), - QDir::toNativeSeparators(savedDataFolder.path())); - - QDir().mkdir(highfidelityDirectory.path()); - - //////////////////////////////////////////////////////////////////////////////// - - // Finally - restore the data folder - QDir().rmdir(highfidelityDirectory.path()); - - highfidelityDirectory.rename(QDir::fromNativeSeparators(savedDataFolder.path()), - QDir::toNativeSeparators(highfidelityDirectory.path())); + testRunner.run(); } void Test::updateTestRailRunResult() { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 5019d91345..f4e7c21408 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -19,6 +19,7 @@ #include "ImageComparer.h" #include "ui/MismatchWindow.h" #include "TestRailInterface.h" +#include "TestRunner.h" class Step { public: @@ -107,6 +108,8 @@ private: ImageComparer _imageComparer; + TestRunner testRunner; + QString _testResultsFolderPath; int _index { 1 }; diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp new file mode 100644 index 0000000000..c1df09762b --- /dev/null +++ b/tools/auto-tester/src/TestRunner.cpp @@ -0,0 +1,51 @@ +// +// Downloader.cpp +// +// Created by Nissim Hadar on 1 Sep 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRunner.h" + +#include + +TestRunner::TestRunner(QObject *parent) : QObject(parent) { +} + +void TestRunner::run() { + saveExistingHighFidelityAppDataFolder(); + + //////////////////////////////////////////////////////////////////////////////// + + restoreHighFidelityAppDataFolder(); +} + +void TestRunner::saveExistingHighFidelityAppDataFolder() { + QString dataDirectory{ "NOT FOUND" }; + +#ifdef Q_OS_WIN + dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; +#endif + + appDataFolder = dataDirectory + "\\High Fidelity"; + + if (!appDataFolder.exists()) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "The High Fidelity data folder was not found in " + dataDirectory); + exit(-1); + } + + // The original folder is saved in a unique name + savedAppDataFolder = dataDirectory + "/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf"; + appDataFolder.rename(QDir::fromNativeSeparators(appDataFolder.path()), QDir::toNativeSeparators(savedAppDataFolder.path())); + + QDir().mkdir(appDataFolder.path()); +} + +void TestRunner::restoreHighFidelityAppDataFolder() { + QDir().rmdir(appDataFolder.path()); + + appDataFolder.rename(QDir::fromNativeSeparators(savedAppDataFolder.path()), QDir::toNativeSeparators(appDataFolder.path())); +} \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h new file mode 100644 index 0000000000..1109ef32ce --- /dev/null +++ b/tools/auto-tester/src/TestRunner.h @@ -0,0 +1,32 @@ +// +// Downloader.h +// +// Created by Nissim Hadar on 1 Sep 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_testRunner_h +#define hifi_testRunner_h + +#include +#include + +class TestRunner : public QObject { +Q_OBJECT +public: + explicit TestRunner(QObject *parent = 0); + + void run(); + + void saveExistingHighFidelityAppDataFolder(); + void restoreHighFidelityAppDataFolder(); + +private: + QDir appDataFolder; + QDir savedAppDataFolder; +}; + +#endif // hifi_testRunner_h \ No newline at end of file From 1ffd005b7705a0e0be54f0f2cd4c7df09663c2c4 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 1 Sep 2018 22:46:49 -0700 Subject: [PATCH 09/90] Can download the High Fidelity installer. --- tools/auto-tester/src/Downloader.cpp | 6 ++-- tools/auto-tester/src/Downloader.h | 2 +- tools/auto-tester/src/Test.cpp | 14 +++----- tools/auto-tester/src/Test.h | 5 --- tools/auto-tester/src/TestRunner.cpp | 33 +++++++++++++++++-- tools/auto-tester/src/TestRunner.h | 9 ++++- tools/auto-tester/src/ui/AutoTester.cpp | 35 ++++++++++---------- tools/auto-tester/src/ui/AutoTester.h | 16 +++++---- tools/auto-tester/src/ui/AutoTester.ui | 44 ++++++++++++------------- 9 files changed, 97 insertions(+), 67 deletions(-) diff --git a/tools/auto-tester/src/Downloader.cpp b/tools/auto-tester/src/Downloader.cpp index 530a3b61bd..cb9863f34d 100644 --- a/tools/auto-tester/src/Downloader.cpp +++ b/tools/auto-tester/src/Downloader.cpp @@ -11,20 +11,20 @@ #include -Downloader::Downloader(QUrl imageUrl, QObject *parent) : QObject(parent) { +Downloader::Downloader(QUrl fileURL, QObject *parent) : QObject(parent) { connect( &_networkAccessManager, SIGNAL (finished(QNetworkReply*)), this, SLOT (fileDownloaded(QNetworkReply*)) ); - QNetworkRequest request(imageUrl); + QNetworkRequest request(fileURL); _networkAccessManager.get(request); } void Downloader::fileDownloaded(QNetworkReply* reply) { QNetworkReply::NetworkError error = reply->error(); if (error != QNetworkReply::NetworkError::NoError) { - QMessageBox::information(0, "Test Aborted", "Failed to download image: " + reply->errorString()); + QMessageBox::information(0, "Test Aborted", "Failed to download file: " + reply->errorString()); return; } diff --git a/tools/auto-tester/src/Downloader.h b/tools/auto-tester/src/Downloader.h index b0ad58fac5..6d1029698f 100644 --- a/tools/auto-tester/src/Downloader.h +++ b/tools/auto-tester/src/Downloader.h @@ -30,7 +30,7 @@ class Downloader : public QObject { Q_OBJECT public: - explicit Downloader(QUrl imageUrl, QObject *parent = 0); + explicit Downloader(QUrl fileURL, QObject *parent = 0); QByteArray downloadedData() const; diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 16f06e20e7..c81ddc591f 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -238,7 +238,7 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch } } - autoTester->downloadImages(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames); + autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames); } void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) { @@ -314,7 +314,7 @@ void Test::createTests() { _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return + // If user canceled then restore previous selection and return if (_snapshotDirectory == "") { _snapshotDirectory = previousSelection; return; @@ -329,7 +329,7 @@ void Test::createTests() { _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return + // If user canceled then restore previous selection and return if (_testsRootDirectory == "") { _testsRootDirectory = previousSelection; return; @@ -456,7 +456,7 @@ bool Test::createFileSetup() { _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return + // If user canceled then restore previous selection and return if (_testDirectory == "") { _testDirectory = previousSelection; return false; @@ -798,7 +798,7 @@ void Test::createTestsOutline() { _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return + // If user canceled then restore previous selection and return if (_testDirectory == "") { _testDirectory = previousSelection; return; @@ -920,10 +920,6 @@ void Test::createTestRailRun() { _testRailInterface.createTestRailRun(outputDirectory); } -void Test::runNow() { - testRunner.run(); -} - void Test::updateTestRailRunResult() { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, "Zipped Test Results (*.zip)"); diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index f4e7c21408..ce4129ed1c 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -19,7 +19,6 @@ #include "ImageComparer.h" #include "ui/MismatchWindow.h" #include "TestRailInterface.h" -#include "TestRunner.h" class Step { public: @@ -65,8 +64,6 @@ public: void createTestRailTestCases(); void createTestRailRun(); - void runNow(); - void updateTestRailRunResult(); void createRecursiveScript(); @@ -108,8 +105,6 @@ private: ImageComparer _imageComparer; - TestRunner testRunner; - QString _testResultsFolderPath; int _index { 1 }; diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index c1df09762b..1c700c987f 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -1,5 +1,5 @@ // -// Downloader.cpp +// TestRunner.cpp // // Created by Nissim Hadar on 1 Sep 2018. // Copyright 2013 High Fidelity, Inc. @@ -10,14 +10,26 @@ #include "TestRunner.h" #include +#include + +#include "ui/AutoTester.h" +extern AutoTester* autoTester; TestRunner::TestRunner(QObject *parent) : QObject(parent) { } void TestRunner::run() { saveExistingHighFidelityAppDataFolder(); + selectTemporaryFolder(); + + QStringList urls; + urls << "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe"; + + QStringList filenames; + filenames << "HighFidelity-Beta-latest-dev.exe"; + + autoTester->downloadFiles(urls, _tempDirectory, filenames); - //////////////////////////////////////////////////////////////////////////////// restoreHighFidelityAppDataFolder(); } @@ -48,4 +60,21 @@ void TestRunner::restoreHighFidelityAppDataFolder() { QDir().rmdir(appDataFolder.path()); appDataFolder.rename(QDir::fromNativeSeparators(savedAppDataFolder.path()), QDir::toNativeSeparators(appDataFolder.path())); +} + +void TestRunner::selectTemporaryFolder() { + QString previousSelection = _tempDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _tempDirectory = + QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_tempDirectory == "") { + _tempDirectory = previousSelection; + return; + } } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 1109ef32ce..17fbbaf24d 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -12,7 +12,9 @@ #define hifi_testRunner_h #include -#include +#include + +#include "Downloader.h" class TestRunner : public QObject { Q_OBJECT @@ -23,10 +25,15 @@ public: void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); + void selectTemporaryFolder(); private: QDir appDataFolder; QDir savedAppDataFolder; + + QString _tempDirectory; + + Downloader* _downloader; }; #endif // hifi_testRunner_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index f4b7f22bec..bd8a5068e9 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -32,7 +32,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.tabWidget->setTabEnabled(3, false); #endif - // helpWindow.textBrowser->setText() + // _helpWindow.textBrowser->setText() } void AutoTester::setup() { @@ -99,7 +99,7 @@ void AutoTester::on_createTestRailRunButton_clicked() { } void AutoTester::on_runNowButton_clicked() { - _test->runNow(); + _testRunner.run(); } void AutoTester::on_updateTestRailRunResultsButton_clicked() { @@ -142,7 +142,7 @@ void AutoTester::on_createXMLScriptRadioButton_clicked() { _test->setTestRailCreateMode(XML); } -void AutoTester::downloadImage(const QUrl& url) { +void AutoTester::downloadFile(const QUrl& url) { _downloaders.emplace_back(new Downloader(url, this)); connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map())); @@ -151,47 +151,46 @@ void AutoTester::downloadImage(const QUrl& url) { ++_index; } -void AutoTester::downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) { +void AutoTester::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) { _directoryName = directoryName; _filenames = filenames; - _numberOfImagesToDownload = URLs.size(); - _numberOfImagesDownloaded = 0; + _numberOfFilesToDownload = URLs.size(); + _numberOfFilesDownloaded = 0; _index = 0; _ui.progressBar->setMinimum(0); - _ui.progressBar->setMaximum(_numberOfImagesToDownload - 1); + _ui.progressBar->setMaximum(_numberOfFilesToDownload - 1); _ui.progressBar->setValue(0); _ui.progressBar->setVisible(true); _downloaders.clear(); - for (int i = 0; i < _numberOfImagesToDownload; ++i) { - QUrl imageURL(URLs[i]); - downloadImage(imageURL); + for (int i = 0; i < _numberOfFilesToDownload; ++i) { + downloadFile(URLs[i]); } - connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveImage(int))); + connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); } -void AutoTester::saveImage(int index) { +void AutoTester::saveFile(int index) { try { QFile file(_directoryName + "/" + _filenames[index]); file.open(QIODevice::WriteOnly); file.write(_downloaders[index]->downloadedData()); file.close(); } catch (...) { - QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]); + QMessageBox::information(0, "Test Aborted", "Failed to save file: " + _filenames[index]); _ui.progressBar->setVisible(false); return; } - ++_numberOfImagesDownloaded; + ++_numberOfFilesDownloaded; - if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { - disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveImage(int))); + if (_numberOfFilesDownloaded == _numberOfFilesToDownload) { + disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); _test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar); } else { - _ui.progressBar->setValue(_numberOfImagesDownloaded); + _ui.progressBar->setValue(_numberOfFilesDownloaded); } } @@ -200,7 +199,7 @@ void AutoTester::about() { } void AutoTester::content() { - helpWindow.show(); + _helpWindow.show(); } void AutoTester::setUserText(const QString& user) { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index f0b5a99bf8..d4f4554fea 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -19,6 +19,8 @@ #include "../Test.h" #include "HelpWindow.h" +#include "../TestRunner.h" + class AutoTester : public QMainWindow { Q_OBJECT @@ -30,8 +32,8 @@ public: void runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user); - void downloadImage(const QUrl& url); - void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); + void downloadFile(const QUrl& url); + void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); void setUserText(const QString& user); QString getSelectedUser(); @@ -70,7 +72,7 @@ private slots: void on_closeButton_clicked(); - void saveImage(int index); + void saveFile(int index); void about(); void content(); @@ -88,13 +90,15 @@ private: // Used to enable passing a parameter to slots QSignalMapper* _signalMapper; - int _numberOfImagesToDownload { 0 }; - int _numberOfImagesDownloaded { 0 }; + int _numberOfFilesToDownload { 0 }; + int _numberOfFilesDownloaded { 0 }; int _index { 0 }; bool _isRunningFromCommandline { false }; - HelpWindow helpWindow; + HelpWindow _helpWindow; + + TestRunner _testRunner; }; #endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index ca14e417c9..0a68a1754d 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -23,7 +23,7 @@ - 235 + 430 620 100 40 @@ -43,7 +43,7 @@ - 1 + 2 @@ -176,25 +176,12 @@ Evaluate - - - - 150 - 230 - 255 - 23 - - - - 24 - - - 150 + 130 180 - 131 + 120 20 @@ -327,9 +314,9 @@ - 160 + 120 80 - 81 + 110 16 @@ -365,9 +352,9 @@ - 160 + 120 40 - 81 + 110 16 @@ -380,6 +367,19 @@ GitHub User + + + + 80 + 630 + 255 + 23 + + + + 24 + + @@ -387,7 +387,7 @@ 0 0 582 - 21 + 26 From 0c8f80026afb261d2d7e03537ec3a4057b4c7d69 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 2 Sep 2018 12:58:18 -0700 Subject: [PATCH 10/90] Simplifying code. --- tools/auto-tester/src/Test.cpp | 36 ++++++++++++++++--------- tools/auto-tester/src/Test.h | 17 +++++++++--- tools/auto-tester/src/ui/AutoTester.cpp | 9 +++---- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index c81ddc591f..be2698502b 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -23,7 +23,10 @@ extern AutoTester* autoTester; #include -Test::Test() { +Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) { + _progressBar = progressBar; + _checkBoxInteractiveMode = checkBoxInteractiveMode; + _mismatchWindow.setModal(true); if (autoTester) { @@ -58,11 +61,11 @@ void Test::zipAndDeleteTestResultsFolder() { _index = 1; } -bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) { - progressBar->setMinimum(0); - progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); - progressBar->setValue(0); - progressBar->setVisible(true); +bool Test::compareImageLists() { + _progressBar->setMinimum(0); + _progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); + _progressBar->setValue(0); + _progressBar->setVisible(true); // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. @@ -74,6 +77,8 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) QImage expectedImage(_expectedImagesFullFilenames[i]); double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + + bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked()); // similarityIndex is set to -100.0 to indicate images are not the same size if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { @@ -117,10 +122,10 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) } } - progressBar->setValue(i); + _progressBar->setValue(i); } - progressBar->setVisible(false); + _progressBar->setVisible(false); return success; } @@ -173,7 +178,13 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFa comparisonImage.save(failureFolderPath + "/" + "Difference Image.png"); } -void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) { +void Test::startTestsEvaluation(const bool isRunningFromCommandLine, + const QString& testFolder, + const QString& branchFromCommandLine, + const QString& userFromCommandLine +) { + _isRunningFromCommandLine = isRunningFromCommandLine; + if (testFolder.isNull()) { // Get list of JPEG images in folder, sorted by name QString previousSelection = _snapshotDirectory; @@ -240,11 +251,10 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames); } - -void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) { - bool success = compareImageLists((!isRunningFromCommandline && interactiveMode), progressBar); +void Test::finishTestsEvaluation() { + bool success = compareImageLists(); - if (!isRunningFromCommandline) { + if (!_isRunningFromCommandLine) { if (success) { QMessageBox::information(0, "Success", "All images are as expected"); } else { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index ce4129ed1c..dc562801bc 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -41,10 +41,14 @@ enum TestRailCreateMode { class Test { public: - Test(); + Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode); - void startTestsEvaluation(const QString& testFolder = QString(), const QString& branchFromCommandLine = QString(), const QString& userFromCommandLine = QString()); - void finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar); + void startTestsEvaluation(const bool isRunningFromCommandLine, + const QString& testFolder = QString(), + const QString& branchFromCommandLine = QString(), + const QString& userFromCommandLine = QString()); + + void finishTestsEvaluation(); void createTests(); @@ -70,7 +74,7 @@ public: void createAllRecursiveScripts(); void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); - bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); + bool compareImageLists(); QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); @@ -93,6 +97,11 @@ public: void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); private: + QProgressBar* _progressBar; + QCheckBox* _checkBoxInteractiveMode; + + bool _isRunningFromCommandLine{ false }; + const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index bd8a5068e9..e07c0e959c 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,12 +36,11 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { } void AutoTester::setup() { - _test = new Test(); + _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); } void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { - _isRunningFromCommandline = true; - _test->startTestsEvaluation(testFolder, branch, user); + _test->startTestsEvaluation(true, testFolder, branch, user); } void AutoTester::on_tabWidget_currentChanged(int index) { @@ -55,7 +54,7 @@ void AutoTester::on_tabWidget_currentChanged(int index) { } void AutoTester::on_evaluateTestsButton_clicked() { - _test->startTestsEvaluation(); + _test->startTestsEvaluation(false); } void AutoTester::on_createRecursiveScriptButton_clicked() { @@ -188,7 +187,7 @@ void AutoTester::saveFile(int index) { if (_numberOfFilesDownloaded == _numberOfFilesToDownload) { disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); - _test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar); + _test->finishTestsEvaluation(); } else { _ui.progressBar->setValue(_numberOfFilesDownloaded); } From abb860087fcfd164c843ae1cb9e3a45f0d5760d0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 2 Sep 2018 20:32:20 -0700 Subject: [PATCH 11/90] Callback after downloading installer complete. --- tools/auto-tester/src/Test.cpp | 2 +- tools/auto-tester/src/TestRunner.cpp | 7 +++++-- tools/auto-tester/src/TestRunner.h | 1 + tools/auto-tester/src/ui/AutoTester.cpp | 12 +++++++++--- tools/auto-tester/src/ui/AutoTester.h | 6 ++++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index be2698502b..645eb2c57c 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -249,7 +249,7 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, } } - autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames); + autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this); } void Test::finishTestsEvaluation() { bool success = compareImageLists(); diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 1c700c987f..407b8333a6 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -28,10 +28,13 @@ void TestRunner::run() { QStringList filenames; filenames << "HighFidelity-Beta-latest-dev.exe"; - autoTester->downloadFiles(urls, _tempDirectory, filenames); + autoTester->downloadFiles(urls, _tempDirectory, filenames, (void *)this); + // Will continue after download complete +} - restoreHighFidelityAppDataFolder(); +void TestRunner::installerDownloadComplete() { + restoreHighFidelityAppDataFolder(); } void TestRunner::saveExistingHighFidelityAppDataFolder() { diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 17fbbaf24d..444dea74cf 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -22,6 +22,7 @@ public: explicit TestRunner(QObject *parent = 0); void run(); + void installerDownloadComplete(); void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index e07c0e959c..2db0bb05cb 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -37,6 +37,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { void AutoTester::setup() { _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); + _testRunner = new TestRunner(); } void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { @@ -98,7 +99,7 @@ void AutoTester::on_createTestRailRunButton_clicked() { } void AutoTester::on_runNowButton_clicked() { - _testRunner.run(); + _testRunner->run(); } void AutoTester::on_updateTestRailRunResultsButton_clicked() { @@ -150,9 +151,10 @@ void AutoTester::downloadFile(const QUrl& url) { ++_index; } -void AutoTester::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) { +void AutoTester::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) { _directoryName = directoryName; _filenames = filenames; + _caller = caller; _numberOfFilesToDownload = URLs.size(); _numberOfFilesDownloaded = 0; @@ -187,7 +189,11 @@ void AutoTester::saveFile(int index) { if (_numberOfFilesDownloaded == _numberOfFilesToDownload) { disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); - _test->finishTestsEvaluation(); + if (_caller == _test) { + _test->finishTestsEvaluation(); + } else if (_caller == _testRunner) { + _testRunner->installerDownloadComplete(); + } } else { _ui.progressBar->setValue(_numberOfFilesDownloaded); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index d4f4554fea..dfa8f2e02a 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -33,7 +33,7 @@ public: void runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user); void downloadFile(const QUrl& url); - void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); + void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller); void setUserText(const QString& user); QString getSelectedUser(); @@ -80,6 +80,7 @@ private slots: private: Ui::AutoTesterClass _ui; Test* _test; + TestRunner* _testRunner; std::vector _downloaders; @@ -98,7 +99,8 @@ private: HelpWindow _helpWindow; - TestRunner _testRunner; + + void* _caller; }; #endif // hifi_AutoTester_h \ No newline at end of file From 82d9fa68597a8d1c489f048a2ef1e482c3b3a5f2 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 3 Sep 2018 15:50:08 -0700 Subject: [PATCH 12/90] Renamed per standard. --- tools/auto-tester/src/TestRailInterface.cpp | 14 +++++++------- tools/auto-tester/src/TestRailInterface.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 9678c52e13..8f2762a84b 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -50,10 +50,10 @@ QString TestRailInterface::getObject(const QString& path) { bool TestRailInterface::setPythonCommand() { if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); - if (!QFile::exists(_pythonPath + "/" + pythonExe)) { - QMessageBox::critical(0, pythonExe, QString("Python executable not found in ") + _pythonPath); + if (!QFile::exists(_pythonPath + "/" + _pythonExe)) { + QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + _pythonPath); } - _pythonCommand = _pythonPath + "/" + pythonExe; + _pythonCommand = _pythonPath + "/" + _pythonExe; return true; } else { QMessageBox::critical(0, "PYTHON_PATH not defined", @@ -228,7 +228,7 @@ bool TestRailInterface::requestTestRailTestCasesDataFromUser() { _url = _testRailTestCasesSelectorWindow.getURL() + "/"; _user = _testRailTestCasesSelectorWindow.getUser(); _password = _testRailTestCasesSelectorWindow.getPassword(); - ////_password = "tutKA76";//// + _projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID()); @@ -246,7 +246,7 @@ bool TestRailInterface::requestTestRailRunDataFromUser() { _url = _testRailRunSelectorWindow.getURL() + "/"; _user = _testRailRunSelectorWindow.getUser(); _password = _testRailRunSelectorWindow.getPassword(); - ////_password = "tutKA76";//// + _projectID = QString::number(_testRailRunSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID()); @@ -264,7 +264,7 @@ bool TestRailInterface::requestTestRailResultsDataFromUser() { _url = _testRailResultsSelectorWindow.getURL() + "/"; _user = _testRailResultsSelectorWindow.getUser(); _password = _testRailResultsSelectorWindow.getPassword(); - ////_password = "tutKA76";//// + _projectID = QString::number(_testRailResultsSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailResultsSelectorWindow.getSuiteID()); @@ -365,8 +365,8 @@ void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirect QMessageBox::Yes | QMessageBox::No).exec() ) { QProcess* process = new QProcess(); - connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); diff --git a/tools/auto-tester/src/TestRailInterface.h b/tools/auto-tester/src/TestRailInterface.h index ba5e94957b..325fa9d643 100644 --- a/tools/auto-tester/src/TestRailInterface.h +++ b/tools/auto-tester/src/TestRailInterface.h @@ -115,7 +115,7 @@ private: QString _userGitHub; QString _branchGitHub; - const QString pythonExe{ "python.exe" }; + const QString _pythonExe{ "python.exe" }; QString _pythonCommand; QStringList _releaseNames; From 6b83d9878849d38bbbaeaf7f9b7a1ad465b66be8 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 3 Sep 2018 15:50:52 -0700 Subject: [PATCH 13/90] Can run installer, but does not wait for completion. --- tools/auto-tester/src/TestRunner.cpp | 34 +++++++++++++++++++++------- tools/auto-tester/src/TestRunner.h | 17 ++++++++++---- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 407b8333a6..688ab849d3 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -19,6 +19,12 @@ TestRunner::TestRunner(QObject *parent) : QObject(parent) { } void TestRunner::run() { + selectTemporaryFolder(); + runInstaller(); + + + + saveExistingHighFidelityAppDataFolder(); selectTemporaryFolder(); @@ -26,7 +32,7 @@ void TestRunner::run() { urls << "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe"; QStringList filenames; - filenames << "HighFidelity-Beta-latest-dev.exe"; + filenames << _installerFilename; autoTester->downloadFiles(urls, _tempDirectory, filenames, (void *)this); @@ -34,9 +40,21 @@ void TestRunner::run() { } void TestRunner::installerDownloadComplete() { + runInstaller(); + restoreHighFidelityAppDataFolder(); } +void TestRunner::runInstaller() { + QProcess installProcess; + + QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempDirectory) }; + + QString installerFullPath = _tempDirectory + "/" + _installerFilename; + qint64 pid; + QProcess::startDetached(installerFullPath, arguments, QString(), &pid); +} + void TestRunner::saveExistingHighFidelityAppDataFolder() { QString dataDirectory{ "NOT FOUND" }; @@ -44,25 +62,25 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; #endif - appDataFolder = dataDirectory + "\\High Fidelity"; + _appDataFolder = dataDirectory + "\\High Fidelity"; - if (!appDataFolder.exists()) { + if (!_appDataFolder.exists()) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "The High Fidelity data folder was not found in " + dataDirectory); exit(-1); } // The original folder is saved in a unique name - savedAppDataFolder = dataDirectory + "/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf"; - appDataFolder.rename(QDir::fromNativeSeparators(appDataFolder.path()), QDir::toNativeSeparators(savedAppDataFolder.path())); + _savedAppDataFolder = dataDirectory + "/" + _uniqueFolderName; + _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); - QDir().mkdir(appDataFolder.path()); + QDir().mkdir(_appDataFolder.path()); } void TestRunner::restoreHighFidelityAppDataFolder() { - QDir().rmdir(appDataFolder.path()); + QDir().rmdir(_appDataFolder.path()); - appDataFolder.rename(QDir::fromNativeSeparators(savedAppDataFolder.path()), QDir::toNativeSeparators(appDataFolder.path())); + _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); } void TestRunner::selectTemporaryFolder() { diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 444dea74cf..e9cd8bbfc0 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -15,26 +15,33 @@ #include #include "Downloader.h" +#include "ui/BusyWindow.h" class TestRunner : public QObject { -Q_OBJECT + Q_OBJECT public: - explicit TestRunner(QObject *parent = 0); + explicit TestRunner(QObject* parent = 0); void run(); void installerDownloadComplete(); + void runInstaller(); void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); void selectTemporaryFolder(); private: - QDir appDataFolder; - QDir savedAppDataFolder; + QDir _appDataFolder; + QDir _savedAppDataFolder; QString _tempDirectory; Downloader* _downloader; + + const QString _uniqueFolderName{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + const QString _installerFilename{ "HighFidelity-Beta-latest-dev.exe" }; + + BusyWindow _busyWindow; }; -#endif // hifi_testRunner_h \ No newline at end of file +#endif // hifi_testRunner_h \ No newline at end of file From 4f8182fc1ff55171d8797f3f52e79850fb306ad0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 3 Sep 2018 20:54:48 -0700 Subject: [PATCH 14/90] Installer waits for completion. --- tools/auto-tester/src/TestRunner.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 688ab849d3..ca3d3147bf 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -46,13 +46,16 @@ void TestRunner::installerDownloadComplete() { } void TestRunner::runInstaller() { - QProcess installProcess; - + // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) + // To allow installation, the installer is run using the `system` command QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempDirectory) }; QString installerFullPath = _tempDirectory + "/" + _installerFilename; - qint64 pid; - QProcess::startDetached(installerFullPath, arguments, QString(), &pid); + QString commandLine = installerFullPath + " /S /D=" + QDir::toNativeSeparators(_tempDirectory); + system(commandLine.toStdString().c_str()); + int i = 34; + //qint64 pid; + //QProcess::startDetached(installerFullPath, arguments, QString(), &pid); } void TestRunner::saveExistingHighFidelityAppDataFolder() { From a5666cb9fddfeea8ff4b3714f0aaf6de6649c9cf Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 3 Sep 2018 23:25:06 -0700 Subject: [PATCH 15/90] Minor typo in comment. --- tools/auto-tester/src/Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 645eb2c57c..60aeaa1414 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -195,7 +195,7 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return + // If user canceled then restore previous selection and return if (_snapshotDirectory == "") { _snapshotDirectory = previousSelection; return; From d668f60ab9a898679ec352de800a3689b57647a0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 3 Sep 2018 23:26:19 -0700 Subject: [PATCH 16/90] Creates snapshot folder. --- tools/auto-tester/src/TestRunner.cpp | 41 +++++++++++++--------------- tools/auto-tester/src/TestRunner.h | 14 ++++++---- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index ca3d3147bf..bd427dd66b 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -1,7 +1,7 @@ // // TestRunner.cpp // -// Created by Nissim Hadar on 1 Sep 2018. +// Created by Nissim Hadar on 1 Sept 2018. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -19,28 +19,23 @@ TestRunner::TestRunner(QObject *parent) : QObject(parent) { } void TestRunner::run() { - selectTemporaryFolder(); - runInstaller(); - - - - saveExistingHighFidelityAppDataFolder(); selectTemporaryFolder(); QStringList urls; - urls << "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe"; + urls << INSTALLER_URL; QStringList filenames; - filenames << _installerFilename; + filenames << INSTALLER_FILENAME; - autoTester->downloadFiles(urls, _tempDirectory, filenames, (void *)this); + autoTester->downloadFiles(urls, _tempFolder, filenames, (void *)this); - // Will continue after download complete + // installerDownloadComplete will run after download complete } void TestRunner::installerDownloadComplete() { runInstaller(); + createSnapshotFolder(); restoreHighFidelityAppDataFolder(); } @@ -48,14 +43,12 @@ void TestRunner::installerDownloadComplete() { void TestRunner::runInstaller() { // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) // To allow installation, the installer is run using the `system` command - QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempDirectory) }; + QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempFolder) }; - QString installerFullPath = _tempDirectory + "/" + _installerFilename; - QString commandLine = installerFullPath + " /S /D=" + QDir::toNativeSeparators(_tempDirectory); + QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; + QString commandLine = QDir::toNativeSeparators(installerFullPath + " /S /D=" + _tempFolder); + system(commandLine.toStdString().c_str()); - int i = 34; - //qint64 pid; - //QProcess::startDetached(installerFullPath, arguments, QString(), &pid); } void TestRunner::saveExistingHighFidelityAppDataFolder() { @@ -74,7 +67,7 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { } // The original folder is saved in a unique name - _savedAppDataFolder = dataDirectory + "/" + _uniqueFolderName; + _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); QDir().mkdir(_appDataFolder.path()); @@ -87,18 +80,22 @@ void TestRunner::restoreHighFidelityAppDataFolder() { } void TestRunner::selectTemporaryFolder() { - QString previousSelection = _tempDirectory; + QString previousSelection = _tempFolder; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - _tempDirectory = + _tempFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, QFileDialog::ShowDirsOnly); // If user canceled then restore previous selection and return - if (_tempDirectory == "") { - _tempDirectory = previousSelection; + if (_tempFolder == "") { + _tempFolder = previousSelection; return; } +} + +void TestRunner::createSnapshotFolder() { + QDir().mkdir(_tempFolder + "/" + SNAPSHOT_FOLDER_NAME); } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index e9cd8bbfc0..69445e0ba4 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -1,7 +1,7 @@ // // Downloader.h // -// Created by Nissim Hadar on 1 Sep 2018. +// Created by Nissim Hadar on 1 Sept 2018. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -29,17 +29,21 @@ public: void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); void selectTemporaryFolder(); + void createSnapshotFolder(); -private: + private: QDir _appDataFolder; QDir _savedAppDataFolder; - QString _tempDirectory; + QString _tempFolder; Downloader* _downloader; - const QString _uniqueFolderName{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; - const QString _installerFilename{ "HighFidelity-Beta-latest-dev.exe" }; + const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; + + const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; + const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; BusyWindow _busyWindow; }; From 06ad3903c48adbef54889797e7d713885222b493 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 10:05:17 -0700 Subject: [PATCH 17/90] Kills High Fidelity processes (which is a good thing in this context). --- tools/auto-tester/src/TestRunner.cpp | 15 ++++++++++++++- tools/auto-tester/src/TestRunner.h | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index bd427dd66b..6754e6ff46 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -36,6 +36,7 @@ void TestRunner::run() { void TestRunner::installerDownloadComplete() { runInstaller(); createSnapshotFolder(); + killProcesses(); restoreHighFidelityAppDataFolder(); } @@ -46,8 +47,8 @@ void TestRunner::runInstaller() { QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempFolder) }; QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; + QString commandLine = QDir::toNativeSeparators(installerFullPath + " /S /D=" + _tempFolder); - system(commandLine.toStdString().c_str()); } @@ -98,4 +99,16 @@ void TestRunner::selectTemporaryFolder() { void TestRunner::createSnapshotFolder() { QDir().mkdir(_tempFolder + "/" + SNAPSHOT_FOLDER_NAME); +} +void TestRunner::killProcesses() { + killProcessByName("assignment-client.exe"); + killProcessByName("domain-server.exe"); + killProcessByName("server-console.exe"); +} + +void TestRunner::killProcessByName(QString processName) { +#ifdef Q_OS_WIN + QString commandLine = "taskkill /im " + processName + " /f >nul"; + system(commandLine.toStdString().c_str()); +#endif } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 69445e0ba4..6e0566f7d5 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -30,6 +30,8 @@ public: void restoreHighFidelityAppDataFolder(); void selectTemporaryFolder(); void createSnapshotFolder(); + void killProcesses(); + void killProcessByName(QString processName); private: QDir _appDataFolder; From 33f3552cbaa8409176375b19459e1ffadc12d2ab Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 13:31:31 -0700 Subject: [PATCH 18/90] Kills running processes (assignment clients, domain server and sand box) prior to installation Runs server processes Runs interface with the top-level recursive test script. --- tools/auto-tester/src/TestRunner.cpp | 47 +++++++++++++++++++++++----- tools/auto-tester/src/TestRunner.h | 6 +++- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 6754e6ff46..06bb5aec9b 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -9,6 +9,7 @@ // #include "TestRunner.h" +#include #include #include @@ -30,15 +31,18 @@ void TestRunner::run() { autoTester->downloadFiles(urls, _tempFolder, filenames, (void *)this); - // installerDownloadComplete will run after download complete + // After download has finished, `installerDownloadComplete` will run after download complete } void TestRunner::installerDownloadComplete() { runInstaller(); createSnapshotFolder(); killProcesses(); + startLocalServerProcesses(); + runInterfaceWithTestScript(); - restoreHighFidelityAppDataFolder(); + killProcesses(); + restoreHighFidelityAppDataFolder(); } void TestRunner::runInstaller() { @@ -48,7 +52,7 @@ void TestRunner::runInstaller() { QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; - QString commandLine = QDir::toNativeSeparators(installerFullPath + " /S /D=" + _tempFolder); + QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_tempFolder); system(commandLine.toStdString().c_str()); } @@ -70,8 +74,6 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { // The original folder is saved in a unique name _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); - - QDir().mkdir(_appDataFolder.path()); } void TestRunner::restoreHighFidelityAppDataFolder() { @@ -98,7 +100,8 @@ void TestRunner::selectTemporaryFolder() { } void TestRunner::createSnapshotFolder() { - QDir().mkdir(_tempFolder + "/" + SNAPSHOT_FOLDER_NAME); + _snapshotFolder = _tempFolder + "/" + SNAPSHOT_FOLDER_NAME; + QDir().mkdir(_snapshotFolder); } void TestRunner::killProcesses() { killProcessByName("assignment-client.exe"); @@ -111,4 +114,34 @@ void TestRunner::killProcessByName(QString processName) { QString commandLine = "taskkill /im " + processName + " /f >nul"; system(commandLine.toStdString().c_str()); #endif -} \ No newline at end of file +} + +void TestRunner::startLocalServerProcesses() { + QDir::setCurrent(_tempFolder); + +#ifdef Q_OS_WIN + QString commandLine; + + commandLine = "start \"domain-server.exe\" domain-server.exe"; + system(commandLine.toStdString().c_str()); + + commandLine = "start \"assignment-client.exe\" assignment-client.exe -n 6"; + system(commandLine.toStdString().c_str()); +#endif + // Give server processes time to stabilize + QThread::sleep(8); +} + +void TestRunner::runInterfaceWithTestScript() { + QDir::setCurrent(_tempFolder); + QString branch = autoTester->getSelectedBranch(); + QString user = autoTester->getSelectedUser(); + +#ifdef Q_OS_WIN + QString commandLine = "interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + user + + "/hifi_tests/" + branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + + _snapshotFolder; + + system(commandLine.toStdString().c_str()); +#endif +} diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 6e0566f7d5..4d95086936 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -13,6 +13,7 @@ #include #include +#include #include "Downloader.h" #include "ui/BusyWindow.h" @@ -32,12 +33,15 @@ public: void createSnapshotFolder(); void killProcesses(); void killProcessByName(QString processName); + void startLocalServerProcesses(); + void runInterfaceWithTestScript(); - private: +private: QDir _appDataFolder; QDir _savedAppDataFolder; QString _tempFolder; + QString _snapshotFolder; Downloader* _downloader; From cfed050b50c1fb43cc2550b3e3e65f749aac0f25 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 15:44:48 -0700 Subject: [PATCH 19/90] Connect download-complete signal before starting downloads. --- tools/auto-tester/src/ui/AutoTester.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 2db0bb05cb..5056e888a7 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -152,6 +152,8 @@ void AutoTester::downloadFile(const QUrl& url) { } void AutoTester::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) { + connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); + _directoryName = directoryName; _filenames = filenames; _caller = caller; @@ -169,8 +171,6 @@ void AutoTester::downloadFiles(const QStringList& URLs, const QString& directory for (int i = 0; i < _numberOfFilesToDownload; ++i) { downloadFile(URLs[i]); } - - connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); } void AutoTester::saveFile(int index) { From ca3c797c842e81338195758917df2032ea6d7b4f Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 15:45:48 -0700 Subject: [PATCH 20/90] Evaluate results. --- tools/auto-tester/src/TestRunner.cpp | 14 ++++++++++---- tools/auto-tester/src/TestRunner.h | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 06bb5aec9b..b598b6e5dc 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -41,6 +41,8 @@ void TestRunner::installerDownloadComplete() { startLocalServerProcesses(); runInterfaceWithTestScript(); + evaluateResults(); + killProcesses(); restoreHighFidelityAppDataFolder(); } @@ -134,14 +136,18 @@ void TestRunner::startLocalServerProcesses() { void TestRunner::runInterfaceWithTestScript() { QDir::setCurrent(_tempFolder); - QString branch = autoTester->getSelectedBranch(); - QString user = autoTester->getSelectedUser(); + _branch = autoTester->getSelectedBranch(); + _user = autoTester->getSelectedUser(); #ifdef Q_OS_WIN - QString commandLine = "interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + user + - "/hifi_tests/" + branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + + QString commandLine = "interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + _snapshotFolder; system(commandLine.toStdString().c_str()); #endif } + +void TestRunner::evaluateResults() { + autoTester->runFromCommandLine(_snapshotFolder, _branch, _user); +} diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 4d95086936..e9214b8cb7 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -16,7 +16,6 @@ #include #include "Downloader.h" -#include "ui/BusyWindow.h" class TestRunner : public QObject { Q_OBJECT @@ -35,6 +34,7 @@ public: void killProcessByName(QString processName); void startLocalServerProcesses(); void runInterfaceWithTestScript(); + void evaluateResults(); private: QDir _appDataFolder; @@ -51,7 +51,8 @@ private: const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; - BusyWindow _busyWindow; + QString _branch; + QString _user; }; #endif // hifi_testRunner_h \ No newline at end of file From d74d3ecab3486cd2624eee7a7820a5f2016272e0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 16:17:33 -0700 Subject: [PATCH 21/90] Create empty difference image if images differ in size. --- tools/auto-tester/src/ui/MismatchWindow.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index 79d2ce9f61..ef6e35ba8a 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -22,6 +22,11 @@ MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { } QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { + // Create an empty difference image if the images differ in size + if (expectedImage.height() != resultImage.height() || expectedImage.width() != resultImage.width()) { + return QPixmap(); + } + // This is an optimization, as QImage.setPixel() is embarrassingly slow unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; From bcec0c8fbd29698139d58f2a842fd6ee0edfb330 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 16:56:20 -0700 Subject: [PATCH 22/90] Fixed reversed boolean. --- tools/auto-tester/src/Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 60aeaa1414..c8b3b2c1f7 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -47,7 +47,7 @@ bool Test::createTestResultsFolderPath(const QString& directory) { void Test::zipAndDeleteTestResultsFolder() { QString zippedResultsFileName { _testResultsFolderPath + ".zip" }; QFileInfo fileInfo(zippedResultsFileName); - if (!fileInfo.exists()) { + if (fileInfo.exists()) { QFile::remove(zippedResultsFileName); } From 825be44eebb0e299fd8c85f47f337abd2585bbbf Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 20:43:45 -0700 Subject: [PATCH 23/90] Hide Windows tab on non-Windows machines. --- tools/auto-tester/src/ui/AutoTester.cpp | 5 +- tools/auto-tester/src/ui/AutoTester.ui | 66 ++++++++++++------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 5056e888a7..0b18f02b10 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -28,11 +28,12 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { connect(_ui.actionAbout, &QAction::triggered, this, &AutoTester::about); connect(_ui.actionContent, &QAction::triggered, this, &AutoTester::content); + // The second tab hides and shows the Windows task bar #ifndef Q_OS_WIN - _ui.tabWidget->setTabEnabled(3, false); + _ui.tabWidget->removeTab(1); #endif - // _helpWindow.textBrowser->setText() + //// _helpWindow.textBrowser->setText() } void AutoTester::setup() { diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 0a68a1754d..8c95bba7d1 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -43,7 +43,7 @@ - 2 + 1 @@ -154,6 +154,37 @@ + + + Windows + + + + + 160 + 130 + 211 + 40 + + + + Hide Windows Taskbar + + + + + + 160 + 200 + 211 + 40 + + + + Show Windows Taskbar + + + Run @@ -279,37 +310,6 @@ - - - Windows - - - - - 160 - 130 - 211 - 40 - - - - Hide Windows Taskbar - - - - - - 160 - 200 - 211 - 40 - - - - Show Windows Taskbar - - - @@ -387,7 +387,7 @@ 0 0 582 - 26 + 21 From 8fa80a465d4164f43051393f728c0d5c13f469da Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 4 Sep 2018 22:49:14 -0700 Subject: [PATCH 24/90] Minor, minor correction. --- tools/auto-tester/src/TestRunner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index b598b6e5dc..abd7d07c6b 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -105,6 +105,7 @@ void TestRunner::createSnapshotFolder() { _snapshotFolder = _tempFolder + "/" + SNAPSHOT_FOLDER_NAME; QDir().mkdir(_snapshotFolder); } + void TestRunner::killProcesses() { killProcessByName("assignment-client.exe"); killProcessByName("domain-server.exe"); From 991beeab19172eb4a9725a53566e46b53e1cf660 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 5 Sep 2018 11:49:17 -0700 Subject: [PATCH 25/90] Created an empty AppData folder for automated testing. --- .../AppDataHighFidelity/Interface.json | 278 ++++++ .../Interface/avatarbookmarks.json | 861 ++++++++++++++++++ .../assignment-client/entities/models.json.gz | Bin 0 -> 112 bytes .../domain-server/AccountInfo.bin | Bin 0 -> 1450 bytes .../domain-server/config.json | 7 + .../domain-server/entities/models.json.gz | Bin 0 -> 112 bytes tools/auto-tester/CMakeLists.txt | 8 + tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 8 files changed, 1155 insertions(+), 1 deletion(-) create mode 100644 tools/auto-tester/AppDataHighFidelity/Interface.json create mode 100644 tools/auto-tester/AppDataHighFidelity/Interface/avatarbookmarks.json create mode 100644 tools/auto-tester/AppDataHighFidelity/assignment-client/entities/models.json.gz create mode 100644 tools/auto-tester/AppDataHighFidelity/domain-server/AccountInfo.bin create mode 100644 tools/auto-tester/AppDataHighFidelity/domain-server/config.json create mode 100644 tools/auto-tester/AppDataHighFidelity/domain-server/entities/models.json.gz diff --git a/tools/auto-tester/AppDataHighFidelity/Interface.json b/tools/auto-tester/AppDataHighFidelity/Interface.json new file mode 100644 index 0000000000..429d6f109e --- /dev/null +++ b/tools/auto-tester/AppDataHighFidelity/Interface.json @@ -0,0 +1,278 @@ +{ + "AddressManager/address": "hifi://localhost/1.07449e-05,0,-0.0174923/0,0,0,1", + "Audio/Desktop/INPUT": "Microphone (Realtek Audio)", + "Audio/Desktop/OUTPUT": "Speakers / Headphones (Realtek Audio)", + "Audio/VR/INPUT": "Microphone (Realtek Audio)", + "Audio/VR/OUTPUT": "Speakers / Headphones (Realtek Audio)", + "Avatar/Avatar/fullAvatarURL": "", + "Avatar/Debug Draw Animation": false, + "Avatar/Debug Draw Base of Support": false, + "Avatar/Debug Draw Default Pose": false, + "Avatar/Debug Draw Position": false, + "Avatar/Disable Eyelid Adjustment": false, + "Avatar/Draw Mesh": true, + "Avatar/Enable Default Motor Control": true, + "Avatar/Enable Inverse Kinematics": true, + "Avatar/Enable LookAt Snapping": true, + "Avatar/Enable Scripted Motor Control": true, + "Avatar/Face Tracking/Auto Mute Microphone": false, + "Avatar/Face Tracking/Binary Eyelid Control": true, + "Avatar/Face Tracking/Couple Eyelids": true, + "Avatar/Face Tracking/Mute Face Tracking": true, + "Avatar/Face Tracking/None": false, + "Avatar/Face Tracking/Use Audio for Mouth": true, + "Avatar/Face Tracking/Use Camera": true, + "Avatar/Face Tracking/Velocity Filter": true, + "Avatar/Fix Gaze (no saccade)": false, + "Avatar/Show Bounding Collision Shapes": false, + "Avatar/Show Detailed Collision": false, + "Avatar/Show IK Chains": false, + "Avatar/Show IK Constraints": false, + "Avatar/Show IK Targets": false, + "Avatar/Show My Eye Vectors": false, + "Avatar/Show Other Eye Vectors": false, + "Avatar/Show Receive Stats": false, + "Avatar/Show SensorToWorld Matrix": false, + "Avatar/Toggle Hips Following": false, + "Avatar/Turn using Head": false, + "Avatar/animGraphURL": "", + "Avatar/attachmentData/size": 0, + "Avatar/avatarEntityData/size": 0, + "Avatar/collisionSoundURL": "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav", + "Avatar/displayName": "", + "Avatar/dominantHand": "right", + "Avatar/flyingHMD": false, + "Avatar/fullAvatarModelName": "Default", + "Avatar/fullAvatarURL": "", + "Avatar/headPitch": 0, + "Avatar/pitchSpeed": 75, + "Avatar/scale": 1, + "Avatar/useSnapTurn": true, + "Avatar/userHeight": 1.7549999952316284, + "Avatar/yawSpeed": 100, + "Developer/Avatar/Debug Draw Animation": false, + "Developer/Avatar/Debug Draw Base of Support": false, + "Developer/Avatar/Debug Draw Default Pose": false, + "Developer/Avatar/Debug Draw Position": false, + "Developer/Avatar/Disable Eyelid Adjustment": false, + "Developer/Avatar/Draw Mesh": true, + "Developer/Avatar/Enable Default Motor Control": true, + "Developer/Avatar/Enable Inverse Kinematics": true, + "Developer/Avatar/Enable LookAt Snapping": true, + "Developer/Avatar/Enable Scripted Motor Control": true, + "Developer/Avatar/Face Tracking/Auto Mute Microphone": false, + "Developer/Avatar/Face Tracking/Binary Eyelid Control": true, + "Developer/Avatar/Face Tracking/Couple Eyelids": true, + "Developer/Avatar/Face Tracking/Mute Face Tracking": true, + "Developer/Avatar/Face Tracking/None": false, + "Developer/Avatar/Face Tracking/Use Audio for Mouth": true, + "Developer/Avatar/Face Tracking/Use Camera": true, + "Developer/Avatar/Face Tracking/Velocity Filter": true, + "Developer/Avatar/Fix Gaze (no saccade)": false, + "Developer/Avatar/Show Bounding Collision Shapes": false, + "Developer/Avatar/Show Detailed Collision": false, + "Developer/Avatar/Show IK Chains": false, + "Developer/Avatar/Show IK Constraints": false, + "Developer/Avatar/Show IK Targets": false, + "Developer/Avatar/Show My Eye Vectors": false, + "Developer/Avatar/Show Other Eye Vectors": false, + "Developer/Avatar/Show Receive Stats": false, + "Developer/Avatar/Show SensorToWorld Matrix": false, + "Developer/Avatar/Toggle Hips Following": false, + "Developer/Avatar/Turn using Head": false, + "Developer/Debug defaultScripts.js": false, + "Developer/Display Crash Options": true, + "Developer/Enable Speech Control API": false, + "Developer/Entities/Show Realtime Entity Stats": false, + "Developer/Hands/Show Hand Targets": false, + "Developer/Network/Disable Activity Logger": false, + "Developer/Physics/Highlight Simulation Ownership": false, + "Developer/Physics/Show Bullet Bounding Boxes": false, + "Developer/Physics/Show Bullet Collision": false, + "Developer/Physics/Show Bullet Constraint Limits": false, + "Developer/Physics/Show Bullet Constraints": false, + "Developer/Physics/Show Bullet Contact Points": false, + "Developer/Picking/Force Coarse Picking": false, + "Developer/Render/Ambient Occlusion": false, + "Developer/Render/Compute Blendshapes": true, + "Developer/Render/Decimate Textures": false, + "Developer/Render/Default Skybox": true, + "Developer/Render/Enable Sparse Texture Management": true, + "Developer/Render/Maximum Texture Memory/1024 MB": false, + "Developer/Render/Maximum Texture Memory/2048 MB": false, + "Developer/Render/Maximum Texture Memory/256 MB": false, + "Developer/Render/Maximum Texture Memory/4 MB": false, + "Developer/Render/Maximum Texture Memory/4096 MB": false, + "Developer/Render/Maximum Texture Memory/512 MB": false, + "Developer/Render/Maximum Texture Memory/6144 MB": false, + "Developer/Render/Maximum Texture Memory/64 MB": false, + "Developer/Render/Maximum Texture Memory/8192 MB": false, + "Developer/Render/Maximum Texture Memory/Automatic Texture Memory": true, + "Developer/Render/OpenVR Threaded Submit": true, + "Developer/Render/Scale Resolution/1": true, + "Developer/Render/Scale Resolution/1/2": false, + "Developer/Render/Scale Resolution/1/3": false, + "Developer/Render/Scale Resolution/1/4": false, + "Developer/Render/Scale Resolution/2/3": false, + "Developer/Render/Shadows": true, + "Developer/Render/Temporal Antialiasing (FXAA if disabled)": true, + "Developer/Render/Throttle FPS If Not Focus": true, + "Developer/Render/World Axes": false, + "Developer/Show Overlays": true, + "Developer/Show Statistics": false, + "Developer/Timing/Log Extra Timing Details": false, + "Developer/Timing/Log Render Pipeline Warnings": false, + "Developer/Timing/Performance Timer/Display Timing Details": false, + "Developer/Timing/Performance Timer/Expand /myAvatar": false, + "Developer/Timing/Performance Timer/Expand /myAvatar/simulation": false, + "Developer/Timing/Performance Timer/Expand /otherAvatar": false, + "Developer/Timing/Performance Timer/Expand /paintGL": false, + "Developer/Timing/Performance Timer/Expand /physics": false, + "Developer/Timing/Performance Timer/Expand /simulation": false, + "Developer/Timing/Performance Timer/Expand /update": false, + "Developer/Timing/Performance Timer/Only Display Top Ten": true, + "Developer/Timing/Show Timer": false, + "Developer/Timing/Suppress Timings Less than 10ms": false, + "Developer/UI/Desktop Tablet Becomes Toolbar": true, + "Developer/UI/HMD Tablet Becomes Toolbar": false, + "Developer/Verbose Logging": false, + "Display/3D TV - Interleaved": false, + "Display/3D TV - Side by Side Stereo": false, + "Display/Desktop": true, + "Display/Fullscreen": false, + "Display/Oculus Rift": false, + "Display/Oculus Rift (Simulator)": false, + "Edit/Allow Selecting of Large Models": true, + "Edit/Allow Selecting of Lights": true, + "Edit/Allow Selecting of Small Models": true, + "Edit/Auto Focus on Select": false, + "Edit/Create Entities As Grabbable (except Zones, Particles, and Lights)": true, + "Edit/Ease Orientation on Focus": false, + "Edit/Show Lights and Particle Systems in Create Mode": true, + "Edit/Show Zones in Create Mode": true, + "Entities/Show Realtime Entity Stats": false, + "Face Tracking/Auto Mute Microphone": false, + "Face Tracking/Binary Eyelid Control": true, + "Face Tracking/Couple Eyelids": true, + "Face Tracking/Mute Face Tracking": true, + "Face Tracking/None": false, + "Face Tracking/Use Audio for Mouth": true, + "Face Tracking/Use Camera": true, + "Face Tracking/Velocity Filter": true, + "Hands/Show Hand Targets": false, + "Leap Motion/desktopHeightOffset": 0.20000000298023224, + "Leap Motion/enabled": false, + "Leap Motion/sensorLocation": "Desktop", + "Maximum Texture Memory/1024 MB": false, + "Maximum Texture Memory/2048 MB": false, + "Maximum Texture Memory/256 MB": false, + "Maximum Texture Memory/4 MB": false, + "Maximum Texture Memory/4096 MB": false, + "Maximum Texture Memory/512 MB": false, + "Maximum Texture Memory/6144 MB": false, + "Maximum Texture Memory/64 MB": false, + "Maximum Texture Memory/8192 MB": false, + "Maximum Texture Memory/Automatic Texture Memory": true, + "Network/Disable Activity Logger": false, + "Perception Neuron/enabled": false, + "Perception Neuron/serverAddress": "localhost", + "Perception Neuron/serverPort": 7001, + "Performance Timer/Display Timing Details": false, + "Performance Timer/Expand /myAvatar": false, + "Performance Timer/Expand /myAvatar/simulation": false, + "Performance Timer/Expand /otherAvatar": false, + "Performance Timer/Expand /paintGL": false, + "Performance Timer/Expand /physics": false, + "Performance Timer/Expand /simulation": false, + "Performance Timer/Expand /update": false, + "Performance Timer/Only Display Top Ten": true, + "Physics/Highlight Simulation Ownership": false, + "Physics/Show Bullet Bounding Boxes": false, + "Physics/Show Bullet Collision": false, + "Physics/Show Bullet Constraint Limits": false, + "Physics/Show Bullet Constraints": false, + "Physics/Show Bullet Contact Points": false, + "Picking/Force Coarse Picking": false, + "Render/Ambient Occlusion": false, + "Render/Compute Blendshapes": true, + "Render/Decimate Textures": false, + "Render/Default Skybox": true, + "Render/Enable Sparse Texture Management": true, + "Render/Maximum Texture Memory/1024 MB": false, + "Render/Maximum Texture Memory/2048 MB": false, + "Render/Maximum Texture Memory/256 MB": false, + "Render/Maximum Texture Memory/4 MB": false, + "Render/Maximum Texture Memory/4096 MB": false, + "Render/Maximum Texture Memory/512 MB": false, + "Render/Maximum Texture Memory/6144 MB": false, + "Render/Maximum Texture Memory/64 MB": false, + "Render/Maximum Texture Memory/8192 MB": false, + "Render/Maximum Texture Memory/Automatic Texture Memory": true, + "Render/OpenVR Threaded Submit": true, + "Render/Scale Resolution/1": true, + "Render/Scale Resolution/1/2": false, + "Render/Scale Resolution/1/3": false, + "Render/Scale Resolution/1/4": false, + "Render/Scale Resolution/2/3": false, + "Render/Shadows": true, + "Render/Temporal Antialiasing (FXAA if disabled)": true, + "Render/Throttle FPS If Not Focus": true, + "Render/World Axes": false, + "RunningScripts": [ + "file:///~//defaultScripts.js" + ], + "SDL2/enabled": true, + "Scale Resolution/1": true, + "Scale Resolution/1/2": false, + "Scale Resolution/1/3": false, + "Scale Resolution/1/4": false, + "Scale Resolution/2/3": false, + "Settings/Ask To Reset Settings on Start": false, + "Settings/Developer Menu": false, + "TabletSounds": "@Variant(\u0000\u0000\u0000\u000b\u0000\u0000\u0000\u0005\u0000\u0000\u0000(\u0000/\u0000s\u0000o\u0000u\u0000n\u0000d\u0000s\u0000/\u0000B\u0000u\u0000t\u0000t\u0000o\u0000n\u00000\u00006\u0000.\u0000w\u0000a\u0000v\u0000\u0000\u0000(\u0000/\u0000s\u0000o\u0000u\u0000n\u0000d\u0000s\u0000/\u0000B\u0000u\u0000t\u0000t\u0000o\u0000n\u00000\u00004\u0000.\u0000w\u0000a\u0000v\u0000\u0000\u0000(\u0000/\u0000s\u0000o\u0000u\u0000n\u0000d\u0000s\u0000/\u0000B\u0000u\u0000t\u0000t\u0000o\u0000n\u00000\u00007\u0000.\u0000w\u0000a\u0000v\u0000\u0000\u0000\"\u0000/\u0000s\u0000o\u0000u\u0000n\u0000d\u0000s\u0000/\u0000T\u0000a\u0000b\u00000\u00001\u0000.\u0000w\u0000a\u0000v\u0000\u0000\u0000\"\u0000/\u0000s\u0000o\u0000u\u0000n\u0000d\u0000s\u0000/\u0000T\u0000a\u0000b\u00000\u00002\u0000.\u0000w\u0000a\u0000v)", + "Timing/Log Extra Timing Details": false, + "Timing/Log Render Pipeline Warnings": false, + "Timing/Performance Timer/Display Timing Details": false, + "Timing/Performance Timer/Expand /myAvatar": false, + "Timing/Performance Timer/Expand /myAvatar/simulation": false, + "Timing/Performance Timer/Expand /otherAvatar": false, + "Timing/Performance Timer/Expand /paintGL": false, + "Timing/Performance Timer/Expand /physics": false, + "Timing/Performance Timer/Expand /simulation": false, + "Timing/Performance Timer/Expand /update": false, + "Timing/Performance Timer/Only Display Top Ten": true, + "Timing/Show Timer": false, + "Timing/Suppress Timings Less than 10ms": false, + "UI/Desktop Tablet Becomes Toolbar": true, + "UI/HMD Tablet Becomes Toolbar": false, + "UserActivityLoggerDisabled": false, + "View/Center Player In View": true, + "View/Enter First Person Mode in HMD": true, + "View/Entity Mode": false, + "View/First Person": true, + "View/Independent Mode": false, + "View/Mirror": false, + "View/Third Person": false, + "WindowGeometry": "@Rect(0 0 1920 1080)", + "WindowRoot.Windows/height": 706, + "WindowRoot.Windows/width": 480, + "WindowState": 0, + "activeDisplayPlugin": "Desktop", + "autoFocusOnSelect": true, + "cameraEaseOnFocus": true, + "desktopLODDecreaseFPS": 30.000001907348633, + "dynamicJitterBuffersEnabled": true, + "firstRun": false, + "hifi.ktx.cache_version": 1, + "hmdLODDecreaseFPS": 34, + "io.highfidelity.attachPoints": "{}", + "io.highfidelity.isEditing": false, + "sessionRunTime": 77, + "showLightsAndParticlesInEditMode": true, + "showZonesInEditMode": true, + "staticJitterBufferFrames": 1, + "toolbar/com.highfidelity.interface.toolbar.system/desktopHeight": 1059, + "toolbar/com.highfidelity.interface.toolbar.system/x": 655, + "toolbar/com.highfidelity.interface.toolbar.system/y": 953, + "toolbar/constrainToolbarToCenterX": true +} diff --git a/tools/auto-tester/AppDataHighFidelity/Interface/avatarbookmarks.json b/tools/auto-tester/AppDataHighFidelity/Interface/avatarbookmarks.json new file mode 100644 index 0000000000..9976036f8e --- /dev/null +++ b/tools/auto-tester/AppDataHighFidelity/Interface/avatarbookmarks.json @@ -0,0 +1,861 @@ +{ + "Anime boy": { + "attachments": [ + ], + "avatarEntites": [ + { + "properties": { + "acceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 6.915350914001465, + "ageAsText": "0 hours 0 minutes 6 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "x": -0.10961885005235672, + "y": -0.19444090127944946, + "z": -0.15760529041290283 + }, + "center": { + "x": 2.6226043701171875e-06, + "y": -0.13999652862548828, + "z": -0.04999971389770508 + }, + "dimensions": { + "x": 0.21924294531345367, + "y": 0.10888873785734177, + "z": 0.2152111530303955 + }, + "tfl": { + "x": 0.10962409526109695, + "y": -0.0855521634221077, + "z": 0.057605862617492676 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-06-06T17:27:53Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "x": 0.21924294531345367, + "y": 0.07768379896879196, + "z": 0.2055898904800415 + }, + "dynamic": false, + "editionNumber": 15, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{5d20c775-a0d7-4163-b158-4e0a784a4625}", + "ignoreForCollisions": false, + "itemArtist": "jyoum", + "itemCategories": "Wearables", + "itemDescription": "Wear these, and others will respect your authoritah.", + "itemLicense": "", + "itemName": "Aviators", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1528306178314655, + "lastEditedBy": "{439a2669-4626-487f-9dcf-2d15e77c69a2}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "x": 2.6226043701171875e-06, + "y": -0.13999652862548828, + "z": -0.04999971389770508 + }, + "localRotation": { + "w": 0.9969173073768616, + "x": -0.07845909893512726, + "y": 0, + "z": 0 + }, + "locked": false, + "marketplaceID": "40d879ec-93f0-4b4a-8c58-dd6349bdb058", + "modelURL": "http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx", + "name": "", + "naturalDimensions": { + "x": 0.1660931408405304, + "y": 0.05885136127471924, + "z": 0.15574991703033447 + }, + "naturalPosition": { + "x": 0, + "y": 1.6633577346801758, + "z": 0.048884183168411255 + }, + "originalTextures": "{\n \"aviator:Eyewear2F\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Diffuse.png\",\n \"aviator:Eyewear2F1\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Specular.png\"\n}\n", + "owningAvatarID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}", + "parentID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}", + "parentJointIndex": 66, + "position": { + "x": 2.6226043701171875e-06, + "y": -0.13999652862548828, + "z": -0.04999971389770508 + }, + "queryAACube": { + "scale": 0.9313028454780579, + "x": -1.4091639518737793, + "y": -10.133878707885742, + "z": 1.9983724355697632 + }, + "registrationPoint": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 2, + "texturesSize": 1310720, + "verticesCount": 982 + }, + "restitution": 0.5, + "rotation": { + "w": 0.9969173073768616, + "x": -0.07845909893512726, + "y": 0, + "z": 0 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + } + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst", + "version": 3 + }, + "Anime girl": { + "attachments": [ + ], + "avatarEntites": [ + { + "properties": { + "acceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 19.66267967224121, + "ageAsText": "0 hours 0 minutes 19 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "x": -0.10536206513643265, + "y": -0.16647332906723022, + "z": -0.12632352113723755 + }, + "center": { + "x": 0, + "y": -0.12999999523162842, + "z": -0.030000001192092896 + }, + "dimensions": { + "x": 0.2107241302728653, + "y": 0.07294666767120361, + "z": 0.1926470398902893 + }, + "tfl": { + "x": 0.10536206513643265, + "y": -0.09352666139602661, + "z": 0.06632351875305176 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-06-05T00:10:37Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "x": 0.2107241302728653, + "y": 0.07294666767120361, + "z": 0.1926470398902893 + }, + "dynamic": false, + "editionNumber": 5, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{1586b83a-2af7-4532-9bfb-82fe3f5d5ce9}", + "ignoreForCollisions": false, + "itemArtist": "moam_00", + "itemCategories": "Wearables", + "itemDescription": "Perfect for side-glancin'.", + "itemLicense": "", + "itemName": "Blacker Fem Glasses", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1528157470041658, + "lastEditedBy": "{425df1a8-289b-42fc-819c-c3b2a12d7165}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "x": 0, + "y": -0.12999999523162842, + "z": -0.029999999329447746 + }, + "localRotation": { + "w": 1, + "x": -2.2351741790771484e-08, + "y": 3.4924596548080444e-10, + "z": 3.725290298461914e-09 + }, + "locked": false, + "marketplaceID": "06781d12-9139-48f4-ac2a-417dde090981", + "modelURL": "http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx", + "name": "Female Glasses 3 by Mario Andrade", + "naturalDimensions": { + "x": 0.16209548711776733, + "y": 0.05611282214522362, + "z": 0.14819003641605377 + }, + "naturalPosition": { + "x": 0, + "y": -7.636845111846924e-08, + "z": 0 + }, + "originalTextures": "{\n \"file49\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Mixed_AO.jpg\",\n \"file81\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Metallic.jpg\",\n \"file84\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Roughness.jpg\",\n \"file86\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Base_Color.jpg\",\n \"file87\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Normal_DirectX.jpg\"\n}\n", + "owningAvatarID": "{1277f725-fbb4-478b-ae79-1241fd90e508}", + "parentID": "{1277f725-fbb4-478b-ae79-1241fd90e508}", + "parentJointIndex": 66, + "position": { + "x": 0, + "y": -0.12999999523162842, + "z": -0.029999999329447746 + }, + "queryAACube": { + "scale": 0.8840523958206177, + "x": -2.6587564945220947, + "y": -10.162277221679688, + "z": -0.9548344016075134 + }, + "registrationPoint": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 5, + "texturesSize": 0, + "verticesCount": 1156 + }, + "restitution": 0.5, + "rotation": { + "w": 1, + "x": -2.2351741790771484e-08, + "y": 3.4924596548080444e-10, + "z": 3.725290298461914e-09 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + } + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst", + "version": 3 + }, + "Last Legends: Male": { + "attachments": [ + ], + "avatarEntites": [ + { + "properties": { + "acceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 321.8835144042969, + "ageAsText": "0 hours 5 minutes 21 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "blue": -0.03950843587517738, + "green": 0.20785385370254517, + "red": -0.04381325840950012, + "x": -0.04381325840950012, + "y": 0.20785385370254517, + "z": -0.03950843587517738 + }, + "center": { + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 + }, + "dimensions": { + "blue": 0.07901687175035477, + "green": 0.044292300939559937, + "red": 0.08762651681900024, + "x": 0.08762651681900024, + "y": 0.044292300939559937, + "z": 0.07901687175035477 + }, + "tfl": { + "blue": 0.03950843587517738, + "green": 0.2521461546421051, + "red": 0.04381325840950012, + "x": 0.04381325840950012, + "y": 0.2521461546421051, + "z": 0.03950843587517738 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-07-26T23:56:46Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "blue": 0.07229919731616974, + "green": 0.06644226610660553, + "red": 0.03022606298327446, + "x": 0.03022606298327446, + "y": 0.06644226610660553, + "z": 0.07229919731616974 + }, + "dynamic": false, + "editionNumber": 58, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{03053239-bb37-4c51-a013-a1772baaeed5}", + "ignoreForCollisions": false, + "itemArtist": "jyoum", + "itemCategories": "Wearables", + "itemDescription": "A cool scifi watch for your avatar!", + "itemLicense": "", + "itemName": "Scifi Watch", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1532649569894305, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 + }, + "localRotation": { + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 + }, + "locked": false, + "marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90", + "modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx", + "name": "Scifi Watch by Jimi", + "naturalDimensions": { + "blue": 0.055614765733480453, + "green": 0.0511094331741333, + "red": 0.023250818252563477, + "x": 0.023250818252563477, + "y": 0.0511094331741333, + "z": 0.055614765733480453 + }, + "naturalPosition": { + "blue": -0.06031447649002075, + "green": 1.4500460624694824, + "red": 0.6493338942527771, + "x": 0.6493338942527771, + "y": 1.4500460624694824, + "z": -0.06031447649002075 + }, + "originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentJointIndex": 16, + "position": { + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 + }, + "queryAACube": { + "scale": 0.3082179129123688, + "x": 495.7716979980469, + "y": 498.345703125, + "z": 498.52044677734375 + }, + "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 5, + "texturesSize": 786432, + "verticesCount": 273 + }, + "restitution": 0.5, + "rotation": { + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + }, + { + "properties": { + "acceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 308.8044128417969, + "ageAsText": "0 hours 5 minutes 8 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "blue": -0.2340194433927536, + "green": -0.07067721337080002, + "red": -0.17002610862255096, + "x": -0.17002610862255096, + "y": -0.07067721337080002, + "z": -0.2340194433927536 + }, + "center": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "dimensions": { + "blue": 0.3883880078792572, + "green": 0.18139348924160004, + "red": 0.34038791060447693, + "x": 0.34038791060447693, + "y": 0.18139348924160004, + "z": 0.3883880078792572 + }, + "tfl": { + "blue": 0.1543685644865036, + "green": 0.11071627587080002, + "red": 0.17036180198192596, + "x": 0.17036180198192596, + "y": 0.11071627587080002, + "z": 0.1543685644865036 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-07-26T23:56:46Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "blue": 0.38838762044906616, + "green": 0.16981728374958038, + "red": 0.33466479182243347, + "x": 0.33466479182243347, + "y": 0.16981728374958038, + "z": 0.38838762044906616 + }, + "dynamic": false, + "editionNumber": 18, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{1bf231ce-3913-4c53-be3c-b1f4094dac51}", + "ignoreForCollisions": false, + "itemArtist": "jyoum", + "itemCategories": "Wearables", + "itemDescription": "A stylish and classic piece of headwear for your avatar.", + "itemLicense": "", + "itemName": "Fedora", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1532649698129709, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "localRotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "locked": false, + "marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc", + "modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx", + "name": "", + "naturalDimensions": { + "blue": 0.320981502532959, + "green": 0.14034485816955566, + "red": 0.2765824794769287, + "x": 0.2765824794769287, + "y": 0.14034485816955566, + "z": 0.320981502532959 + }, + "naturalPosition": { + "blue": 0.022502630949020386, + "green": 1.7460365295410156, + "red": 0.000143393874168396, + "x": 0.000143393874168396, + "y": 1.7460365295410156, + "z": 0.022502630949020386 + }, + "originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentJointIndex": 66, + "position": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "queryAACube": { + "scale": 1.6202316284179688, + "x": 495.21051025390625, + "y": 498.5577697753906, + "z": 497.6370849609375 + }, + "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 2, + "texturesSize": 327680, + "verticesCount": 719 + }, + "restitution": 0.5, + "rotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + } + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst", + "version": 3 + }, + "Last legends Female": { + "attachments": [ + ], + "avatarEntites": [ + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/8d823be5-6197-4418-b984-eb94160ed956-v1/LLFemale_Clothes.fst", + "version": 3 + }, + "Matthew": { + "attachments": [ + ], + "avatarEntites": [ + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst", + "version": 3 + }, + "Priscilla": { + "attachments": [ + ], + "avatarEntites": [ + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst", + "version": 3 + }, + "Woody": { + "attachments": [ + ], + "avatarEntites": [ + ], + "avatarScale": 1, + "avatarUrl": "http://mpassets.highfidelity.com/ad348528-de38-420c-82bb-054cb22163f5-v1/mannequin.fst", + "version": 3 + } +} diff --git a/tools/auto-tester/AppDataHighFidelity/assignment-client/entities/models.json.gz b/tools/auto-tester/AppDataHighFidelity/assignment-client/entities/models.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..1eeeac50ac0e8c4fe65f92715881e661cdd91953 GIT binary patch literal 112 zcmb2|=3oE=?$ytN7?@P2ADMJy!<9vk&it`a)n;Xu?O2f==b7f2mSq^AA*Cb4J@tbR zBbR1qw2Er9)XZ6`ucIWEtGt}GWa*4oQBg}IUd&pm5~U`!bk;1@Wh&RS9KR~`Y&Utf Q>M0Y$v20O=N(Kf70N)cTNB{r; literal 0 HcmV?d00001 diff --git a/tools/auto-tester/AppDataHighFidelity/domain-server/AccountInfo.bin b/tools/auto-tester/AppDataHighFidelity/domain-server/AccountInfo.bin new file mode 100644 index 0000000000000000000000000000000000000000..645e34895e5c2ae38571d19a60c21df84c880b12 GIT binary patch literal 1450 zcmaiuX*d)J9EbliWDyXqirx)88es;s{=7|CAKh> zBjk=;YebG7Q;j3-rehG<dt}>pTS1HE7EiYrSuWTvl;>I*HM&!jW zQF^;JC>UK&QL*QLl`~)>=6uz{euwwhQzNZxEK;lngD; zU}IlZ50Qdg_G#^+%-96}TyTChjkG3Sd77^{k@e0kQ(4Vxx>j52LE7599bdxF^k7DC zL5aEuJBJc_m)W{@eN^tj=SuDz_M?2p)e*;LO>w4!y3seywe_dxv279TT=%hO`%1;5 z{QQ#%I0aWS>(pnJ*3z}%xqBAZ`3?8BF4)LzUfSWZr(J$tuRjrbnu*w0=UIH%uL%u8 zTJ24?;^qZs|KOw=lD|QUx^(Nu{G?E%1OgDq1O$*eFUT-cx9jFeelbfGK9Q;n+!Q=1 zyksC@q2k6^sd9bIQJz);%J95I*2a;-KKXRp)YhJIGT%UBQQ?v73k+sD(dFr<+YJ@Z z&X)hCsJeToSbv17bEHO*+b`&D>^0#vN69K{$S^HZTcC%xXyLLJD_qe%QMX8>S#Qp@FLf+ z$qKKOuD6C(v}Rq&Rm@Sn!?W#d@h}#UksJ=pkd9>2H6{`|JOjp1VFxw23<)Qv80!#H zT$J0or(#;Cv?!D>lvA(kyVx@Mruf$Eg+ZJpt+<7UkaYKOU#9BuN_Q!9G3Ub5K6E51 zi;hcwDzrX!;MeCSEstp$sWH}SdYO%&{m!qp61Q6a@E-F?-n#0rp1_BnuOjUkXRG?H zVaRw(ToHHKP~`iFnm8+U$bl_TX)M)$sK(Oic=w@YHtc>V-$HVdOl%jOAOv=MemJ~_ z=>3~m7c6#ey&p#ky^*0^_2Mep!pfJYULMd?LW@nD&CpK#g3eDS$YX~@<3DCxdY?pP z2k$-^cLa5?GpQ8fpx%qFpMh~w+*RRSqWwZc3)4Ri4vk#5HwOsYT!ua+Uk<=oRf zEt__XDpen1TCzwTnbhn}QWBU!IY~P)v9tu|wbgL*(zdz`rnV<)KIack!@U0#8OdQI z343_VBs0Ct9k!8@?=|YZ<>E7v&$s(eZa@W7-WM1pUd6BR2auc!{$8zgVqK7hZ}ZdE zuZx=Fbq@A_!LW%^>de**+TrNI7M$x)$MczObLpX%@9R1n7Rn~8KYDx3`1%FPVWC3u zK@D`WW0gM3-rR1q2-`a3fa{$Yi?2J5&3FNvYH}2YGpJ< z?$-5)<(3e)O-1K7CaKDx5MG}rYGh10w3pSnr*!4hzQ2PzQ20s_DSC}0U@fCoAR00n*mQ!oQ_pa32$ Qfed)SeP24@fdT>l0D=~Uga7~l literal 0 HcmV?d00001 diff --git a/tools/auto-tester/AppDataHighFidelity/domain-server/config.json b/tools/auto-tester/AppDataHighFidelity/domain-server/config.json new file mode 100644 index 0000000000..d662d89f28 --- /dev/null +++ b/tools/auto-tester/AppDataHighFidelity/domain-server/config.json @@ -0,0 +1,7 @@ +{ + "metaverse": { + "automatic_networking": "full", + "id": "17b1cb9c-08c4-45aa-9257-163ad3913529" + }, + "version": 2.2 +} diff --git a/tools/auto-tester/AppDataHighFidelity/domain-server/entities/models.json.gz b/tools/auto-tester/AppDataHighFidelity/domain-server/entities/models.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..1eeeac50ac0e8c4fe65f92715881e661cdd91953 GIT binary patch literal 112 zcmb2|=3oE=?$ytN7?@P2ADMJy!<9vk&it`a)n;Xu?O2f==b7f2mSq^AA*Cb4J@tbR zBbR1qw2Er9)XZ6`ucIWEtGt}GWa*4oQBg}IUd&pm5~U`!bk;1@Wh&RS9KR~`Y&Utf Q>M0Y$v20O=N(Kf70N)cTNB{r; literal 0 HcmV?d00001 diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index 1546a35f4c..7705dc4361 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -48,4 +48,12 @@ if (WIN32) POST_BUILD COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> \"$\"" ) + + # add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities) + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" + ) + endif () \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 0b18f02b10..5fa475c7ce 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -32,7 +32,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { #ifndef Q_OS_WIN _ui.tabWidget->removeTab(1); #endif - + //// Coming soon... //// _helpWindow.textBrowser->setText() } From c4182a81467070bcff128e8265acb7ac56d7375f Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 5 Sep 2018 16:05:53 -0700 Subject: [PATCH 26/90] Corrected comment. --- tools/auto-tester/src/Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index c8b3b2c1f7..27e54251eb 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -313,7 +313,7 @@ void Test::includeTest(QTextStream& textStream, const QString& testPathname) { } void Test::createTests() { - // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on + // Rename files sequentially, as ExpectedResult_00000.png, ExpectedResult_00001.png and so on // Any existing expected result images will be deleted QString previousSelection = _snapshotDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); From 8a2292ce0763d8b10ed4d725a7899a7313253b6e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 5 Sep 2018 16:09:40 -0700 Subject: [PATCH 27/90] Set user and branch, and request temporary folder at the beginning. Kill processes BEFORE running the downloaded installerDownloadComplete Copy an empty, but valid, AppData/High Fidelity folder. Remove previous snapshots. --- tools/auto-tester/src/TestRunner.cpp | 93 ++++++++++++++++++++++------ tools/auto-tester/src/TestRunner.h | 2 + 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index abd7d07c6b..dfe4e4fea3 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -20,9 +20,17 @@ TestRunner::TestRunner(QObject *parent) : QObject(parent) { } void TestRunner::run() { - saveExistingHighFidelityAppDataFolder(); + // Initial setup + _branch = autoTester->getSelectedBranch(); + _user = autoTester->getSelectedUser(); + + // Everything will be written to this folder selectTemporaryFolder(); + + // This will be restored at the end of the tests + saveExistingHighFidelityAppDataFolder(); + // Download the latest High Fidelity installer QStringList urls; urls << INSTALLER_URL; @@ -31,19 +39,23 @@ void TestRunner::run() { autoTester->downloadFiles(urls, _tempFolder, filenames, (void *)this); - // After download has finished, `installerDownloadComplete` will run after download complete + // `installerDownloadComplete` will run after download has completed } void TestRunner::installerDownloadComplete() { - runInstaller(); - createSnapshotFolder(); + // Kill any existing processes that would interfere with installation killProcesses(); + + runInstaller(); + + createSnapshotFolder(); + startLocalServerProcesses(); runInterfaceWithTestScript(); + killProcesses(); evaluateResults(); - killProcesses(); restoreHighFidelityAppDataFolder(); } @@ -76,11 +88,13 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { // The original folder is saved in a unique name _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); + + // Copy an "empty" AppData folder (i.e. no entities) + copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); } void TestRunner::restoreHighFidelityAppDataFolder() { - QDir().rmdir(_appDataFolder.path()); - + _appDataFolder.removeRecursively(); _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); } @@ -103,7 +117,21 @@ void TestRunner::selectTemporaryFolder() { void TestRunner::createSnapshotFolder() { _snapshotFolder = _tempFolder + "/" + SNAPSHOT_FOLDER_NAME; - QDir().mkdir(_snapshotFolder); + + // Just delete all PNGs from the folder if it already exists + if (QDir(_snapshotFolder).exists()) { + // Note that we cannot use just a `png` filter, as the filenames include periods + QDirIterator it(_snapshotFolder.toStdString().c_str()); + while (it.hasNext()) { + QString filename = it.next(); + if (filename.right(4) == ".png") { + QFile::remove(filename); + } + } + + } else { + QDir().mkdir(_snapshotFolder); + } } void TestRunner::killProcesses() { @@ -120,15 +148,13 @@ void TestRunner::killProcessByName(QString processName) { } void TestRunner::startLocalServerProcesses() { - QDir::setCurrent(_tempFolder); - #ifdef Q_OS_WIN QString commandLine; - commandLine = "start \"domain-server.exe\" domain-server.exe"; + commandLine = "start \"domain-server.exe\" " + QDir::toNativeSeparators(_tempFolder) + "\\domain-server.exe"; system(commandLine.toStdString().c_str()); - commandLine = "start \"assignment-client.exe\" assignment-client.exe -n 6"; + commandLine = "start \"assignment-client.exe\" " + QDir::toNativeSeparators(_tempFolder) + "\\assignment-client.exe -n 6"; system(commandLine.toStdString().c_str()); #endif // Give server processes time to stabilize @@ -136,14 +162,11 @@ void TestRunner::startLocalServerProcesses() { } void TestRunner::runInterfaceWithTestScript() { - QDir::setCurrent(_tempFolder); - _branch = autoTester->getSelectedBranch(); - _user = autoTester->getSelectedUser(); - #ifdef Q_OS_WIN - QString commandLine = "interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + - "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + - _snapshotFolder; + QString commandLine = QDir::toNativeSeparators(_tempFolder) + + "\\interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + + _snapshotFolder; system(commandLine.toStdString().c_str()); #endif @@ -152,3 +175,35 @@ void TestRunner::runInterfaceWithTestScript() { void TestRunner::evaluateResults() { autoTester->runFromCommandLine(_snapshotFolder, _branch, _user); } + +// Copies a folder recursively +void TestRunner::copyFolder(const QString& source, const QString& destination) { + try { + if (!QFileInfo(source).isDir()) { + // just a file copy + QFile::copy(source, destination); + } else { + QDir destinationDir(destination); + if (!destinationDir.cdUp()) { + throw("'source '" + source + "'seems to be a root folder"); + } + + if (!destinationDir.mkdir(QFileInfo(destination).fileName())) { + throw("Could not create destination folder '" + destination + "'"); + } + + QStringList fileNames = + QDir(source).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + + foreach (const QString& fileName, fileNames) { + copyFolder(QString(source + "/" + fileName), QString(destination + "/" + fileName)); + } + } + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index e9214b8cb7..087e5f089b 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -36,6 +36,8 @@ public: void runInterfaceWithTestScript(); void evaluateResults(); + void copyFolder(const QString& source, const QString& destination); + private: QDir _appDataFolder; QDir _savedAppDataFolder; From 21268be9a4f8595cc20894489df5d2e72097c0f4 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 5 Sep 2018 21:08:33 -0700 Subject: [PATCH 28/90] Do not close when evaluation complete. --- tools/auto-tester/src/Test.cpp | 20 +++++++++++++------- tools/auto-tester/src/Test.h | 4 +++- tools/auto-tester/src/TestRunner.cpp | 10 +++++++--- tools/auto-tester/src/TestRunner.h | 1 + tools/auto-tester/src/main.cpp | 2 +- tools/auto-tester/src/ui/AutoTester.cpp | 14 +++++++++++--- tools/auto-tester/src/ui/AutoTester.h | 8 +++++++- 7 files changed, 43 insertions(+), 16 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 27e54251eb..f3526b67a2 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -78,7 +78,7 @@ bool Test::compareImageLists() { double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical - bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked()); + bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked() && !_isRunningInAutomaticTestRun); // similarityIndex is set to -100.0 to indicate images are not the same size if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { @@ -178,14 +178,16 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFa comparisonImage.save(failureFolderPath + "/" + "Difference Image.png"); } -void Test::startTestsEvaluation(const bool isRunningFromCommandLine, - const QString& testFolder, +void Test::startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, const QString& branchFromCommandLine, const QString& userFromCommandLine ) { _isRunningFromCommandLine = isRunningFromCommandLine; + _isRunningInAutomaticTestRun = isRunningInAutomaticTestRun; - if (testFolder.isNull()) { + if (snapshotDirectory.isNull()) { // Get list of JPEG images in folder, sorted by name QString previousSelection = _snapshotDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -201,8 +203,8 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, return; } } else { - _snapshotDirectory = testFolder; - _exitWhenComplete = true; + _snapshotDirectory = snapshotDirectory; + _exitWhenComplete = (isRunningFromCommandLine && !isRunningInAutomaticTestRun); } // Quit if test results folder could not be created @@ -254,7 +256,7 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, void Test::finishTestsEvaluation() { bool success = compareImageLists(); - if (!_isRunningFromCommandLine) { + if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) { if (success) { QMessageBox::information(0, "Success", "All images are as expected"); } else { @@ -267,6 +269,10 @@ void Test::finishTestsEvaluation() { if (_exitWhenComplete) { exit(0); } + + if (_isRunningInAutomaticTestRun) { + autoTester->automaticTestRunEvaluationComplete(); + } } bool Test::isAValidDirectory(const QString& pathname) { diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index dc562801bc..e68ff4b295 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -44,7 +44,8 @@ public: Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode); void startTestsEvaluation(const bool isRunningFromCommandLine, - const QString& testFolder = QString(), + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory = QString(), const QString& branchFromCommandLine = QString(), const QString& userFromCommandLine = QString()); @@ -101,6 +102,7 @@ private: QCheckBox* _checkBoxInteractiveMode; bool _isRunningFromCommandLine{ false }; + bool _isRunningInAutomaticTestRun{ false }; const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index dfe4e4fea3..c45af2d0ee 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -56,7 +56,7 @@ void TestRunner::installerDownloadComplete() { evaluateResults(); - restoreHighFidelityAppDataFolder(); + // The High Fidelity AppData folder will be restored after evaluation has completed } void TestRunner::runInstaller() { @@ -173,7 +173,7 @@ void TestRunner::runInterfaceWithTestScript() { } void TestRunner::evaluateResults() { - autoTester->runFromCommandLine(_snapshotFolder, _branch, _user); + autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } // Copies a folder recursively @@ -206,4 +206,8 @@ void TestRunner::copyFolder(const QString& source, const QString& destination) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); exit(-1); } -} \ No newline at end of file +} + +void TestRunner::automaticTestRunEvaluationComplete() { + restoreHighFidelityAppDataFolder(); +} diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 087e5f089b..e674293ba2 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -35,6 +35,7 @@ public: void startLocalServerProcesses(); void runInterfaceWithTestScript(); void evaluateResults(); + void automaticTestRunEvaluationComplete(); void copyFolder(const QString& source, const QString& destination); diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 03b8cf51ff..ac4b4593c5 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) { autoTester->setup(); if (!testFolder.isNull()) { - autoTester->runFromCommandLine(testFolder, branch, user); + autoTester->startTestsEvaluation(true ,false, testFolder, branch, user); } else { autoTester->show(); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 5fa475c7ce..b76bb6b7ab 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -41,8 +41,12 @@ void AutoTester::setup() { _testRunner = new TestRunner(); } -void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { - _test->startTestsEvaluation(true, testFolder, branch, user); +void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user) { + _test->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); } void AutoTester::on_tabWidget_currentChanged(int index) { @@ -56,7 +60,7 @@ void AutoTester::on_tabWidget_currentChanged(int index) { } void AutoTester::on_evaluateTestsButton_clicked() { - _test->startTestsEvaluation(false); + _test->startTestsEvaluation(false, false); } void AutoTester::on_createRecursiveScriptButton_clicked() { @@ -103,6 +107,10 @@ void AutoTester::on_runNowButton_clicked() { _testRunner->run(); } +void AutoTester::automaticTestRunEvaluationComplete() { + _testRunner->automaticTestRunEvaluationComplete(); +} + void AutoTester::on_updateTestRailRunResultsButton_clicked() { _test->updateTestRailRunResult(); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index dfa8f2e02a..02acf7f7f8 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -30,7 +30,13 @@ public: void setup(); - void runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user); + void startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user); + + void automaticTestRunEvaluationComplete(); void downloadFile(const QUrl& url); void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller); From e9a1da832e6cb651a9237aa7aefd91faa1badd70 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 6 Sep 2018 16:04:15 -0700 Subject: [PATCH 29/90] Deal with no High Fidelity folder --- tools/auto-tester/src/TestRunner.cpp | 27 +++++++++++++-------------- tools/auto-tester/src/ui/AutoTester.h | 1 - 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index c45af2d0ee..cb6755e70f 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -29,7 +29,7 @@ void TestRunner::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); - + // Download the latest High Fidelity installer QStringList urls; urls << INSTALLER_URL; @@ -79,23 +79,22 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { _appDataFolder = dataDirectory + "\\High Fidelity"; - if (!_appDataFolder.exists()) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "The High Fidelity data folder was not found in " + dataDirectory); - exit(-1); + if (_appDataFolder.exists()) { + // The original folder is saved in a unique name + _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; + _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); } - // The original folder is saved in a unique name - _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; - _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); - // Copy an "empty" AppData folder (i.e. no entities) copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); } void TestRunner::restoreHighFidelityAppDataFolder() { _appDataFolder.removeRecursively(); - _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); + + if (_savedAppDataFolder != QDir()) { + _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); + } } void TestRunner::selectTemporaryFolder() { @@ -176,6 +175,10 @@ void TestRunner::evaluateResults() { autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } +void TestRunner::automaticTestRunEvaluationComplete() { + restoreHighFidelityAppDataFolder(); +} + // Copies a folder recursively void TestRunner::copyFolder(const QString& source, const QString& destination) { try { @@ -207,7 +210,3 @@ void TestRunner::copyFolder(const QString& source, const QString& destination) { exit(-1); } } - -void TestRunner::automaticTestRunEvaluationComplete() { - restoreHighFidelityAppDataFolder(); -} diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 02acf7f7f8..1418365c07 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -105,7 +105,6 @@ private: HelpWindow _helpWindow; - void* _caller; }; From 543c1c926543a3e0de9a8a4b67f4b3b0cd9f63db Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 6 Sep 2018 22:31:49 -0700 Subject: [PATCH 30/90] Install into High Fidelity folder. --- tools/auto-tester/src/TestRunner.cpp | 17 +++++++++++------ tools/auto-tester/src/TestRunner.h | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index cb6755e70f..4ca263a7f3 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -62,11 +62,13 @@ void TestRunner::installerDownloadComplete() { void TestRunner::runInstaller() { // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) // To allow installation, the installer is run using the `system` command - QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_tempFolder) }; + QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; - QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_tempFolder); + QString commandLine = + QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); + system(commandLine.toStdString().c_str()); } @@ -112,6 +114,8 @@ void TestRunner::selectTemporaryFolder() { _tempFolder = previousSelection; return; } + + _installationFolder = _tempFolder + "/High Fidelity"; } void TestRunner::createSnapshotFolder() { @@ -150,10 +154,11 @@ void TestRunner::startLocalServerProcesses() { #ifdef Q_OS_WIN QString commandLine; - commandLine = "start \"domain-server.exe\" " + QDir::toNativeSeparators(_tempFolder) + "\\domain-server.exe"; + commandLine = "start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\""; system(commandLine.toStdString().c_str()); - commandLine = "start \"assignment-client.exe\" " + QDir::toNativeSeparators(_tempFolder) + "\\assignment-client.exe -n 6"; + commandLine = + "start \"assignment-client.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\" -n 6"; system(commandLine.toStdString().c_str()); #endif // Give server processes time to stabilize @@ -162,8 +167,8 @@ void TestRunner::startLocalServerProcesses() { void TestRunner::runInterfaceWithTestScript() { #ifdef Q_OS_WIN - QString commandLine = QDir::toNativeSeparators(_tempFolder) + - "\\interface.exe --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + + QString commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + + "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + _snapshotFolder; diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index e674293ba2..2d9058e71a 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -46,6 +46,8 @@ private: QString _tempFolder; QString _snapshotFolder; + QString _installationFolder; + Downloader* _downloader; const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; From 4a702ed8d49c99149f3df44ba8a764a992a887a2 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 8 Sep 2018 21:27:21 -0700 Subject: [PATCH 31/90] add build number to the automated test results --- tools/auto-tester/src/Test.cpp | 8 +- tools/auto-tester/src/Test.h | 2 +- tools/auto-tester/src/TestRunner.cpp | 98 +++++++++++++++++++++---- tools/auto-tester/src/TestRunner.h | 6 +- tools/auto-tester/src/ui/AutoTester.cpp | 4 +- tools/auto-tester/src/ui/AutoTester.h | 2 +- 6 files changed, 97 insertions(+), 23 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index f3526b67a2..d3aa310d7d 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -44,7 +44,7 @@ bool Test::createTestResultsFolderPath(const QString& directory) { return QDir().mkdir(_testResultsFolderPath); } -void Test::zipAndDeleteTestResultsFolder() { +QString Test::zipAndDeleteTestResultsFolder() { QString zippedResultsFileName { _testResultsFolderPath + ".zip" }; QFileInfo fileInfo(zippedResultsFileName); if (fileInfo.exists()) { @@ -59,6 +59,8 @@ void Test::zipAndDeleteTestResultsFolder() { //In all cases, for the next evaluation _testResultsFolderPath = ""; _index = 1; + + return zippedResultsFileName; } bool Test::compareImageLists() { @@ -264,14 +266,14 @@ void Test::finishTestsEvaluation() { } } - zipAndDeleteTestResultsFolder(); + QString zippedFolderName = zipAndDeleteTestResultsFolder(); if (_exitWhenComplete) { exit(0); } if (_isRunningInAutomaticTestRun) { - autoTester->automaticTestRunEvaluationComplete(); + autoTester->automaticTestRunEvaluationComplete(zippedFolderName); } } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index e68ff4b295..c6ef83e7e8 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -86,7 +86,7 @@ public: void appendTestResultsToFile(const QString& testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); bool createTestResultsFolderPath(const QString& directory); - void zipAndDeleteTestResultsFolder(); + QString zipAndDeleteTestResultsFolder(); static bool isAValidDirectory(const QString& pathname); QString extractPathFromTestsDown(const QString& fullPath); diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 4ca263a7f3..5c7bb97055 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -16,7 +16,7 @@ #include "ui/AutoTester.h" extern AutoTester* autoTester; -TestRunner::TestRunner(QObject *parent) : QObject(parent) { +TestRunner::TestRunner(QObject* parent) : QObject(parent) { } void TestRunner::run() { @@ -30,14 +30,14 @@ void TestRunner::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); - // Download the latest High Fidelity installer + // Download the latest High Fidelity installer and build XML. QStringList urls; - urls << INSTALLER_URL; + urls << INSTALLER_URL << BUILD_XML_URL; QStringList filenames; - filenames << INSTALLER_FILENAME; + filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; - autoTester->downloadFiles(urls, _tempFolder, filenames, (void *)this); + autoTester->downloadFiles(urls, _tempFolder, filenames, (void*)this); // `installerDownloadComplete` will run after download has completed } @@ -47,7 +47,7 @@ void TestRunner::installerDownloadComplete() { killProcesses(); runInstaller(); - + createSnapshotFolder(); startLocalServerProcesses(); @@ -63,9 +63,9 @@ void TestRunner::runInstaller() { // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) // To allow installation, the installer is run using the `system` command QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; - + QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; - + QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); @@ -88,7 +88,7 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { } // Copy an "empty" AppData folder (i.e. no entities) - copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); + copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); } void TestRunner::restoreHighFidelityAppDataFolder() { @@ -106,8 +106,8 @@ void TestRunner::selectTemporaryFolder() { parent += "/"; } - _tempFolder = - QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, QFileDialog::ShowDirsOnly); + _tempFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, + QFileDialog::ShowDirsOnly); // If user canceled then restore previous selection and return if (_tempFolder == "") { @@ -168,9 +168,9 @@ void TestRunner::startLocalServerProcesses() { void TestRunner::runInterfaceWithTestScript() { #ifdef Q_OS_WIN QString commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + - "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + - "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + - _snapshotFolder; + "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + + _snapshotFolder; system(commandLine.toStdString().c_str()); #endif @@ -180,10 +180,78 @@ void TestRunner::evaluateResults() { autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } -void TestRunner::automaticTestRunEvaluationComplete() { +void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { + addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); } +void TestRunner::addBuildNumberToResults(QString zippedFolderName) { + try { + QDomDocument domDocument; + QString filename{ _tempFolder + "/" + BUILD_XML_FILENAME }; + QFile file(filename); + if (!file.open(QIODevice::ReadOnly) || !domDocument.setContent(&file)) { + throw QString("Could not open " + filename); + } + + QString platformOfInterest; +#ifdef Q_OS_WIN + platformOfInterest = "windows"; +#else if Q_OS_MAC + platformOfInterest = "mac"; +#endif + QDomElement element = domDocument.documentElement(); + + // Verify first element is "projects" + if (element.tagName() != "projects") { + throw("File seems to be in wrong format"); + } + + element = element.firstChild().toElement(); + if (element.tagName() != "project") { + throw("File seems to be in wrong format"); + } + + if (element.attribute("name") != "interface") { + throw("File is not from 'interface' build"); + } + + // Now loop over the platforms + while (!element.isNull()) { + element = element.firstChild().toElement(); + QString sdf = element.tagName(); + if (element.tagName() != "platform" || element.attribute("name") != platformOfInterest) { + continue; + } + + // Next element should be the build + element = element.firstChild().toElement(); + if (element.tagName() != "build") { + throw("File seems to be in wrong format"); + } + + // Next element should be the version + element = element.firstChild().toElement(); + if (element.tagName() != "version") { + throw("File seems to be in wrong format"); + } + + // Add the build number to the end of the filename + QString build = element.text(); + QStringList filenameParts = zippedFolderName.split("."); + QString augmentedFilename = filenameParts[0] + "_" + build + "." + filenameParts[1]; + QFile::rename(zippedFolderName, augmentedFilename); + } + + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + // Copies a folder recursively void TestRunner::copyFolder(const QString& source, const QString& destination) { try { diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 2d9058e71a..5a5cbf1cf3 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -35,7 +35,8 @@ public: void startLocalServerProcesses(); void runInterfaceWithTestScript(); void evaluateResults(); - void automaticTestRunEvaluationComplete(); + void automaticTestRunEvaluationComplete(QString zippedFolderName); + void addBuildNumberToResults(QString zippedFolderName); void copyFolder(const QString& source, const QString& destination); @@ -56,6 +57,9 @@ private: const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; + const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; + const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; + QString _branch; QString _user; }; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index b76bb6b7ab..3c8be24f69 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -107,8 +107,8 @@ void AutoTester::on_runNowButton_clicked() { _testRunner->run(); } -void AutoTester::automaticTestRunEvaluationComplete() { - _testRunner->automaticTestRunEvaluationComplete(); +void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName) { + _testRunner->automaticTestRunEvaluationComplete(zippedFolderName); } void AutoTester::on_updateTestRailRunResultsButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 1418365c07..6c8641fa49 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -36,7 +36,7 @@ public: const QString& branch, const QString& user); - void automaticTestRunEvaluationComplete(); + void automaticTestRunEvaluationComplete(QString zippedFolderName); void downloadFile(const QUrl& url); void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller); From b881d342d56627c262ff1486cff8ce65302c1bcf Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 8 Sep 2018 21:28:09 -0700 Subject: [PATCH 32/90] Minor cleanup. --- tools/auto-tester/src/TestRailInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 8f2762a84b..e6f12100df 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -800,6 +800,7 @@ void TestRailInterface::createTestSuiteXML(const QString& testDirectory, QDomElement suiteName = _document.createElement("name"); suiteName.appendChild( _document.createTextNode("Test Suite - " + QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm"))); + topLevelSection.appendChild(suiteName); // This is the first call to 'process'. This is then called recursively to build the full XML tree From 1f99917069091c50438b41e8b6dc96a12be21609 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 10 Sep 2018 09:12:28 -0700 Subject: [PATCH 33/90] Improved killing of old processes. --- tools/auto-tester/src/TestRunner.cpp | 56 +++++++++++++++++++++++----- tools/auto-tester/src/TestRunner.h | 1 - 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 5c7bb97055..4075defb6f 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -16,6 +16,11 @@ #include "ui/AutoTester.h" extern AutoTester* autoTester; +#ifdef Q_OS_WIN +#include +#include +#endif + TestRunner::TestRunner(QObject* parent) : QObject(parent) { } @@ -138,15 +143,48 @@ void TestRunner::createSnapshotFolder() { } void TestRunner::killProcesses() { - killProcessByName("assignment-client.exe"); - killProcessByName("domain-server.exe"); - killProcessByName("server-console.exe"); -} - -void TestRunner::killProcessByName(QString processName) { #ifdef Q_OS_WIN - QString commandLine = "taskkill /im " + processName + " /f >nul"; - system(commandLine.toStdString().c_str()); + try { + QStringList processesToKill = QStringList() << "interface.exe" << "assignment-client.exe" << "domain-server.exe" << "server-console.exe"; + + // Loop until all pending processes to kill have actually died + QStringList pendingProcessesToKill; + do { + pendingProcessesToKill.clear(); + + // Get list of running tasks + HANDLE processSnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (processSnapHandle == INVALID_HANDLE_VALUE) { + throw("Process snapshot creation failure"); + } + + PROCESSENTRY32 processEntry32; + processEntry32.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(processSnapHandle, &processEntry32)) { + CloseHandle(processSnapHandle); + throw("Process32First failed"); + } + + // Kill any task in the list + do { + foreach (QString process, processesToKill) + if (QString(processEntry32.szExeFile) == process) { + QString commandLine = "taskkill /im " + process + " /f >nul"; + system(commandLine.toStdString().c_str()); + pendingProcessesToKill << process; + } + } while (Process32Next(processSnapHandle, &processEntry32)); + + QThread::sleep(2); + } while (!pendingProcessesToKill.isEmpty()); + + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } #endif } @@ -239,7 +277,7 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) { // Add the build number to the end of the filename QString build = element.text(); QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = filenameParts[0] + "_" + build + "." + filenameParts[1]; + QString augmentedFilename = filenameParts[0] + "(" + build + ")." + filenameParts[1]; QFile::rename(zippedFolderName, augmentedFilename); } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 5a5cbf1cf3..7b4e8c901a 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -31,7 +31,6 @@ public: void selectTemporaryFolder(); void createSnapshotFolder(); void killProcesses(); - void killProcessByName(QString processName); void startLocalServerProcesses(); void runInterfaceWithTestScript(); void evaluateResults(); From 3f7c1403e7c9a021dfaf2cd42136853c858579f8 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 10 Sep 2018 15:01:01 -0700 Subject: [PATCH 34/90] First version of scheduled tests. --- tools/auto-tester/src/TestRunner.cpp | 148 ++++++++++---- tools/auto-tester/src/TestRunner.h | 32 ++- tools/auto-tester/src/ui/AutoTester.cpp | 36 +++- tools/auto-tester/src/ui/AutoTester.h | 3 + tools/auto-tester/src/ui/AutoTester.ui | 252 +++++++++++++++++++++++- 5 files changed, 418 insertions(+), 53 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 4075defb6f..e40e9a0986 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -21,17 +21,58 @@ extern AutoTester* autoTester; #include #endif -TestRunner::TestRunner(QObject* parent) : QObject(parent) { +TestRunner::TestRunner(std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector timeEdits, + QLabel* workingFolderLabel, + QObject* parent) : + QObject(parent) +{ + _dayCheckboxes = dayCheckboxes; + _timeEditCheckboxes = timeEditCheckboxes; + _timeEdits = timeEdits; + _workingFolderLabel = workingFolderLabel; +} + +TestRunner::~TestRunner() { + disconnect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); +} + +void TestRunner::setWorkingFolder() { + // Everything will be written to this folder + QString previousSelection = _workingFolder; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, + QFileDialog::ShowDirsOnly); + + // If user canceled then restore previous selection and return + if (_workingFolder == "") { + _workingFolder = previousSelection; + return; + } + + _installationFolder = _workingFolder + "/High Fidelity"; + + autoTester->enableRunTabControls(); + _workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); + + // The time is checked every 30 seconds for automatic test start + _timer = new QTimer(this); + connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); + _timer->start(30 * 1000); //time specified in ms } void TestRunner::run() { + _automatedTestIsRunning = true; + // Initial setup _branch = autoTester->getSelectedBranch(); _user = autoTester->getSelectedUser(); - // Everything will be written to this folder - selectTemporaryFolder(); - // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); @@ -42,7 +83,7 @@ void TestRunner::run() { QStringList filenames; filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; - autoTester->downloadFiles(urls, _tempFolder, filenames, (void*)this); + autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); // `installerDownloadComplete` will run after download has completed } @@ -69,7 +110,7 @@ void TestRunner::runInstaller() { // To allow installation, the installer is run using the `system` command QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; - QString installerFullPath = _tempFolder + "/" + INSTALLER_FILENAME; + QString installerFullPath = _workingFolder + "/" + INSTALLER_FILENAME; QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); @@ -96,35 +137,8 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); } -void TestRunner::restoreHighFidelityAppDataFolder() { - _appDataFolder.removeRecursively(); - - if (_savedAppDataFolder != QDir()) { - _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); - } -} - -void TestRunner::selectTemporaryFolder() { - QString previousSelection = _tempFolder; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _tempFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, - QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_tempFolder == "") { - _tempFolder = previousSelection; - return; - } - - _installationFolder = _tempFolder + "/High Fidelity"; -} - void TestRunner::createSnapshotFolder() { - _snapshotFolder = _tempFolder + "/" + SNAPSHOT_FOLDER_NAME; + _snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME; // Just delete all PNGs from the folder if it already exists if (QDir(_snapshotFolder).exists()) { @@ -145,7 +159,10 @@ void TestRunner::createSnapshotFolder() { void TestRunner::killProcesses() { #ifdef Q_OS_WIN try { - QStringList processesToKill = QStringList() << "interface.exe" << "assignment-client.exe" << "domain-server.exe" << "server-console.exe"; + QStringList processesToKill = QStringList() << "interface.exe" + << "assignment-client.exe" + << "domain-server.exe" + << "server-console.exe"; // Loop until all pending processes to kill have actually died QStringList pendingProcessesToKill; @@ -167,12 +184,12 @@ void TestRunner::killProcesses() { // Kill any task in the list do { - foreach (QString process, processesToKill) - if (QString(processEntry32.szExeFile) == process) { - QString commandLine = "taskkill /im " + process + " /f >nul"; - system(commandLine.toStdString().c_str()); - pendingProcessesToKill << process; - } + foreach (QString process, processesToKill) + if (QString(processEntry32.szExeFile) == process) { + QString commandLine = "taskkill /im " + process + " /f >nul"; + system(commandLine.toStdString().c_str()); + pendingProcessesToKill << process; + } } while (Process32Next(processSnapHandle, &processEntry32)); QThread::sleep(2); @@ -200,7 +217,7 @@ void TestRunner::startLocalServerProcesses() { system(commandLine.toStdString().c_str()); #endif // Give server processes time to stabilize - QThread::sleep(8); + QThread::sleep(12); } void TestRunner::runInterfaceWithTestScript() { @@ -221,12 +238,14 @@ void TestRunner::evaluateResults() { void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); + + _automatedTestIsRunning = false; } void TestRunner::addBuildNumberToResults(QString zippedFolderName) { try { QDomDocument domDocument; - QString filename{ _tempFolder + "/" + BUILD_XML_FILENAME }; + QString filename{ _workingFolder + "/" + BUILD_XML_FILENAME }; QFile file(filename); if (!file.open(QIODevice::ReadOnly) || !domDocument.setContent(&file)) { throw QString("Could not open " + filename); @@ -290,6 +309,14 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) { } } +void TestRunner::restoreHighFidelityAppDataFolder() { + _appDataFolder.removeRecursively(); + + if (_savedAppDataFolder != QDir()) { + _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); + } +} + // Copies a folder recursively void TestRunner::copyFolder(const QString& source, const QString& destination) { try { @@ -321,3 +348,38 @@ void TestRunner::copyFolder(const QString& source, const QString& destination) { exit(-1); } } + +void TestRunner::checkTime() { + // No processing is done if a test is running + if (_automatedTestIsRunning) { + return; + } + + QDateTime now = QDateTime::currentDateTime(); + + // Check day of week + if (!_dayCheckboxes.at(now.date().dayOfWeek() - 1)->isChecked()) { + return; + } + + // Check the time + bool timeToRun{ false }; + QTime time = now.time(); + int h = time.hour(); + int m = time.minute(); + + for (int i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { + bool is = _timeEditCheckboxes[i]->isChecked(); + int hh = _timeEdits[i]->time().hour(); + int mm = _timeEdits[i]->time().minute(); + if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) && + (_timeEdits[i]->time().minute() == now.time().minute())) { + timeToRun = true; + break; + } + } + + if (timeToRun) { + run(); + } +} \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 7b4e8c901a..605d56c88d 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -11,24 +11,36 @@ #ifndef hifi_testRunner_h #define hifi_testRunner_h -#include +#include #include +#include +#include #include +#include +#include #include "Downloader.h" class TestRunner : public QObject { Q_OBJECT public: - explicit TestRunner(QObject* parent = 0); + explicit TestRunner(std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector timeEdits, + QLabel* workingFolderLabel, + QObject* parent = 0); + ~TestRunner(); + + void setWorkingFolder(); void run(); + void installerDownloadComplete(); void runInstaller(); void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); - void selectTemporaryFolder(); + void createSnapshotFolder(); void killProcesses(); void startLocalServerProcesses(); @@ -39,11 +51,16 @@ public: void copyFolder(const QString& source, const QString& destination); +private slots: + void checkTime(); + private: + bool _automatedTestIsRunning{ false }; + QDir _appDataFolder; QDir _savedAppDataFolder; - QString _tempFolder; + QString _workingFolder; QString _snapshotFolder; QString _installationFolder; @@ -61,6 +78,13 @@ private: QString _branch; QString _user; + + std::vector _dayCheckboxes; + std::vector _timeEditCheckboxes; + std::vector _timeEdits; + QLabel* _workingFolderLabel; + + QTimer* _timer; }; #endif // hifi_testRunner_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 3c8be24f69..951ee1acd7 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -32,13 +32,35 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { #ifndef Q_OS_WIN _ui.tabWidget->removeTab(1); #endif - //// Coming soon... + //// Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() } void AutoTester::setup() { _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); - _testRunner = new TestRunner(); + + std::vector dayCheckboxes; + dayCheckboxes.emplace_back(_ui.mondayCheckBox); + dayCheckboxes.emplace_back(_ui.tuesdayCheckBox); + dayCheckboxes.emplace_back(_ui.wednesdayCheckBox); + dayCheckboxes.emplace_back(_ui.thursdayCheckBox); + dayCheckboxes.emplace_back(_ui.fridayCheckBox); + dayCheckboxes.emplace_back(_ui.saturdayCheckBox); + dayCheckboxes.emplace_back(_ui.sundayCheckBox); + + std::vector timeEditCheckboxes; + timeEditCheckboxes.emplace_back(_ui.timeEdit1checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit2checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit3checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit4checkBox); + + std::vector timeEdits; + timeEdits.emplace_back(_ui.timeEdit1); + timeEdits.emplace_back(_ui.timeEdit2); + timeEdits.emplace_back(_ui.timeEdit3); + timeEdits.emplace_back(_ui.timeEdit4); + + _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel); } void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, @@ -103,6 +125,16 @@ void AutoTester::on_createTestRailRunButton_clicked() { _test->createTestRailRun(); } +void AutoTester::on_setWorkingFolderButton_clicked() { + _testRunner->setWorkingFolder(); +} + +void AutoTester::enableRunTabControls() { + _ui.runNowButton->setEnabled(true); + _ui.daysGroupBox->setEnabled(true); + _ui.timesGroupBox->setEnabled(true); +} + void AutoTester::on_runNowButton_clicked() { _testRunner->run(); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 6c8641fa49..8ef238d953 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -47,6 +47,8 @@ public: void setBranchText(const QString& branch); QString getSelectedBranch(); + void enableRunTabControls(); + private slots: void on_tabWidget_currentChanged(int index); @@ -66,6 +68,7 @@ private slots: void on_createTestRailTestCasesButton_clicked(); void on_createTestRailRunButton_clicked(); + void on_setWorkingFolderButton_clicked(); void on_runNowButton_clicked(); void on_updateTestRailRunResultsButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 8c95bba7d1..bc8fa27af6 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -43,7 +43,7 @@ - 1 + 2 @@ -190,11 +190,14 @@ Run + + false + - 200 - 200 - 93 + 10 + 70 + 161 28 @@ -202,6 +205,247 @@ Run now + + + false + + + + 20 + 150 + 91 + 241 + + + + Days + + + + + 10 + 210 + 80 + 17 + + + + Sunday + + + + + + 10 + 90 + 80 + 17 + + + + Wednesday + + + + + + 10 + 60 + 80 + 17 + + + + Tuesday + + + + + + 10 + 120 + 80 + 17 + + + + Thursday + + + + + + 10 + 150 + 80 + 17 + + + + Friday + + + + + + 10 + 180 + 80 + 17 + + + + Saturday + + + + + + 10 + 30 + 80 + 17 + + + + Monday + + + + + + false + + + + 130 + 150 + 181 + 191 + + + + Times + + + + + 30 + 20 + 118 + 22 + + + + + + + 30 + 60 + 118 + 22 + + + + + + + 30 + 100 + 118 + 22 + + + + + + + 30 + 140 + 118 + 22 + + + + + + + 10 + 23 + 21 + 17 + + + + + + + + + + 10 + 63 + 21 + 17 + + + + + + + + + + 10 + 103 + 21 + 17 + + + + + + + + + + 10 + 143 + 21 + 17 + + + + + + + + + + + 10 + 20 + 161 + 28 + + + + Set Working Folder + + + + + + 190 + 20 + 321 + 31 + + + + ####### + + From cbc53f1e5dfb5d41e95d5175f12945c9414f3b02 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 12 Sep 2018 17:37:02 -0700 Subject: [PATCH 35/90] WIP. --- tools/auto-tester/src/TestRunner.cpp | 32 +++++++++++++++ tools/auto-tester/src/TestRunner.h | 7 ++++ tools/auto-tester/src/ui/AutoTester.cpp | 17 +++++++- tools/auto-tester/src/ui/AutoTester.h | 3 ++ tools/auto-tester/src/ui/AutoTester.ui | 54 ++++++++++++++++++++----- 5 files changed, 102 insertions(+), 11 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index e40e9a0986..dddc30e8a8 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -56,6 +56,7 @@ void TestRunner::setWorkingFolder() { } _installationFolder = _workingFolder + "/High Fidelity"; + _logFile.setFileName(_workingFolder + "/log.txt"); autoTester->enableRunTabControls(); _workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); @@ -67,6 +68,7 @@ void TestRunner::setWorkingFolder() { } void TestRunner::run() { + _testStartDateTime = QDateTime::currentDateTime(); _automatedTestIsRunning = true; // Initial setup @@ -83,12 +85,20 @@ void TestRunner::run() { QStringList filenames; filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; + updateStatusLabel("Downloading installer"); + autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); // `installerDownloadComplete` will run after download has completed } void TestRunner::installerDownloadComplete() { + appendLog(QString("Test started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + + QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + _testStartDateTime.date().toString("ddd, MMM d, yyyy")); + + updateStatusLabel("Installing"); + // Kill any existing processes that would interfere with installation killProcesses(); @@ -96,6 +106,8 @@ void TestRunner::installerDownloadComplete() { createSnapshotFolder(); + updateStatusLabel("Running tests"); + startLocalServerProcesses(); runInterfaceWithTestScript(); killProcesses(); @@ -232,6 +244,7 @@ void TestRunner::runInterfaceWithTestScript() { } void TestRunner::evaluateResults() { + updateStatusLabel("Evaluating results"); autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } @@ -239,6 +252,7 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); + updateStatusLabel("Testing complete"); _automatedTestIsRunning = false; } @@ -382,4 +396,22 @@ void TestRunner::checkTime() { if (timeToRun) { run(); } +} + +void TestRunner::updateStatusLabel(const QString& message) { + autoTester->updateStatusLabel(message); +} + +void TestRunner::appendLog(const QString& message) { + if (!_logFile.open(QIODevice::Append | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open the log file"); + exit(-1); + } + + _logFile.write(message.toStdString().c_str()); + _logFile.write("\n"); + _logFile.close(); + + autoTester->appendLogWindow(message); } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 605d56c88d..e13b0be070 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -51,6 +51,9 @@ public: void copyFolder(const QString& source, const QString& destination); + void updateStatusLabel(const QString& message); + void appendLog(const QString& message); + private slots: void checkTime(); @@ -85,6 +88,10 @@ private: QLabel* _workingFolderLabel; QTimer* _timer; + + QFile _logFile; + + QDateTime _testStartDateTime; }; #endif // hifi_testRunner_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 951ee1acd7..05eee2957a 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -32,7 +32,11 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { #ifndef Q_OS_WIN _ui.tabWidget->removeTab(1); #endif - //// Coming soon to an auto-tester near you... + + _ui.statusLabel->setText(""); + _ui.plainTextEdit->setReadOnly(true); + + // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() } @@ -67,7 +71,8 @@ void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, const bool isRunningInAutomaticTestRun, const QString& snapshotDirectory, const QString& branch, - const QString& user) { + const QString& user +) { _test->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); } @@ -263,3 +268,11 @@ void AutoTester::setBranchText(const QString& branch) { QString AutoTester::getSelectedBranch() { return _ui.branchTextEdit->toPlainText(); } + +void AutoTester::updateStatusLabel(const QString& status) { + _ui.statusLabel->setText(status); +} + +void AutoTester::appendLogWindow(const QString& message) { + _ui.plainTextEdit->appendPlainText(message); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 8ef238d953..de0622c670 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -49,6 +49,9 @@ public: void enableRunTabControls(); + void updateStatusLabel(const QString& status); + void appendLogWindow(const QString& message); + private slots: void on_tabWidget_currentChanged(int index); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index bc8fa27af6..fa9569a768 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 582 - 734 + 707 + 796 @@ -23,8 +23,8 @@ - 430 - 620 + 470 + 660 100 40 @@ -38,8 +38,8 @@ 30 140 - 521 - 461 + 631 + 501 @@ -320,7 +320,7 @@ 130 150 - 181 + 161 191 @@ -446,6 +446,42 @@ ####### + + + + 300 + 120 + 311 + 331 + + + + + + + 300 + 80 + 41 + 31 + + + + Status: + + + + + + 350 + 80 + 271 + 31 + + + + ####### + + @@ -615,7 +651,7 @@ 80 - 630 + 670 255 23 @@ -630,7 +666,7 @@ 0 0 - 582 + 707 21 From f3bc8bdc1637606bc1e2042af7190b87bad821a4 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 13 Sep 2018 07:53:42 -0700 Subject: [PATCH 36/90] WIP - adding Runner process. --- tools/auto-tester/src/TestRunner.cpp | 73 +++++++++++++++++++--------- tools/auto-tester/src/TestRunner.h | 32 ++++++++---- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index dddc30e8a8..88e7e5b695 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -21,6 +21,15 @@ extern AutoTester* autoTester; #include #endif +static QLabel* _workingFolderLabel; +static QString _workingFolder; + +static const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; +static const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; + +static const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; +static const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; + TestRunner::TestRunner(std::vector dayCheckboxes, std::vector timeEditCheckboxes, std::vector timeEdits, @@ -68,7 +77,7 @@ void TestRunner::setWorkingFolder() { } void TestRunner::run() { - _testStartDateTime = QDateTime::currentDateTime(); + runner->_testStartDateTime = QDateTime::currentDateTime(); _automatedTestIsRunning = true; // Initial setup @@ -78,26 +87,24 @@ void TestRunner::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); - // Download the latest High Fidelity installer and build XML. - QStringList urls; - urls << INSTALLER_URL << BUILD_XML_URL; + QThread* thread = new QThread; - QStringList filenames; - filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; - - updateStatusLabel("Downloading installer"); - - autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); - - // `installerDownloadComplete` will run after download has completed + Runner* runner = new Runner(); + runner->moveToThread(thread); + connect(runner, SIGNAL(error(QString)), this, SLOT(errorString(QString))); + connect(thread, SIGNAL(started()), runner, SLOT(process())); + connect(runner, SIGNAL(finished()), thread, SLOT(quit())); + connect(runner, SIGNAL(finished()), runner, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread->start(); } void TestRunner::installerDownloadComplete() { - appendLog(QString("Test started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + - QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - _testStartDateTime.date().toString("ddd, MMM d, yyyy")); + appendLog(QString("Test started at ") + QString::number(runner->_testStartDateTime.time().hour()) + ":" + + QString("%1").arg(runner->_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + runner->_testStartDateTime.date().toString("ddd, MMM d, yyyy")); - updateStatusLabel("Installing"); + autoTester->updateStatusLabel("Installing"); // Kill any existing processes that would interfere with installation killProcesses(); @@ -106,7 +113,7 @@ void TestRunner::installerDownloadComplete() { createSnapshotFolder(); - updateStatusLabel("Running tests"); + autoTester->updateStatusLabel("Running tests"); startLocalServerProcesses(); runInterfaceWithTestScript(); @@ -244,7 +251,7 @@ void TestRunner::runInterfaceWithTestScript() { } void TestRunner::evaluateResults() { - updateStatusLabel("Evaluating results"); + autoTester->updateStatusLabel("Evaluating results"); autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } @@ -252,7 +259,7 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); - updateStatusLabel("Testing complete"); + autoTester->updateStatusLabel("Testing complete"); _automatedTestIsRunning = false; } @@ -398,10 +405,6 @@ void TestRunner::checkTime() { } } -void TestRunner::updateStatusLabel(const QString& message) { - autoTester->updateStatusLabel(message); -} - void TestRunner::appendLog(const QString& message) { if (!_logFile.open(QIODevice::Append | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), @@ -414,4 +417,26 @@ void TestRunner::appendLog(const QString& message) { _logFile.close(); autoTester->appendLogWindow(message); -} \ No newline at end of file +} + +Runner::Runner() { +} + +Runner::~Runner() { +} + +void Runner::process() { + // Download the latest High Fidelity installer and build XML. + QStringList urls; + urls << INSTALLER_URL << BUILD_XML_URL; + + QStringList filenames; + filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; + + autoTester->updateStatusLabel("Downloading installer"); + + autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); + + // `installerDownloadComplete` will run after download has completed + emit finished(); +} diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index e13b0be070..00bf42f8b3 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -21,6 +21,8 @@ #include "Downloader.h" +class Runner; + class TestRunner : public QObject { Q_OBJECT public: @@ -51,7 +53,6 @@ public: void copyFolder(const QString& source, const QString& destination); - void updateStatusLabel(const QString& message); void appendLog(const QString& message); private slots: @@ -63,7 +64,6 @@ private: QDir _appDataFolder; QDir _savedAppDataFolder; - QString _workingFolder; QString _snapshotFolder; QString _installationFolder; @@ -73,24 +73,38 @@ private: const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; - const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; - const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; - - const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; - const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; - QString _branch; QString _user; std::vector _dayCheckboxes; std::vector _timeEditCheckboxes; std::vector _timeEdits; - QLabel* _workingFolderLabel; QTimer* _timer; QFile _logFile; + Runner* runner; +}; +class Runner : public QObject { + Q_OBJECT + +public: + friend TestRunner; + + Runner(); + ~Runner(); + + void updateStatusLabel(const QString& message); + +public slots: + void process(); + +signals: + void finished(); + void error(QString err); + +private: QDateTime _testStartDateTime; }; From 35eb01952408629d992800ccc2f5b2ece6fd9969 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 13 Sep 2018 15:34:10 -0700 Subject: [PATCH 37/90] Can run installer in a thread. --- tools/auto-tester/src/TestRunner.cpp | 51 ++++++++++++++++++++-------- tools/auto-tester/src/TestRunner.h | 44 ++++++++++++++++-------- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index dddc30e8a8..0d777fbecb 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -32,6 +32,8 @@ TestRunner::TestRunner(std::vector dayCheckboxes, _timeEditCheckboxes = timeEditCheckboxes; _timeEdits = timeEdits; _workingFolderLabel = workingFolderLabel; + + thread = new QThread(); } TestRunner::~TestRunner() { @@ -103,7 +105,32 @@ void TestRunner::installerDownloadComplete() { killProcesses(); runInstaller(); +} +void TestRunner::runInstaller() { + // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) + // To allow installation, the installer is run using the `system` command + + QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; + + QString installerFullPath = _workingFolder + "/" + INSTALLER_FILENAME; + + QString commandLine = + QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); + + + worker = new Worker(commandLine); + worker->moveToThread(thread); + connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString))); + connect(thread, SIGNAL(started()), worker, SLOT(process())); + connect(worker, SIGNAL(finished()), this, SLOT(installationComplete())); + connect(worker, SIGNAL(finished()), thread, SLOT(quit())); + connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread->start(); +} + +void TestRunner::installationComplete() { createSnapshotFolder(); updateStatusLabel("Running tests"); @@ -117,19 +144,6 @@ void TestRunner::installerDownloadComplete() { // The High Fidelity AppData folder will be restored after evaluation has completed } -void TestRunner::runInstaller() { - // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) - // To allow installation, the installer is run using the `system` command - QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; - - QString installerFullPath = _workingFolder + "/" + INSTALLER_FILENAME; - - QString commandLine = - QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); - - system(commandLine.toStdString().c_str()); -} - void TestRunner::saveExistingHighFidelityAppDataFolder() { QString dataDirectory{ "NOT FOUND" }; @@ -414,4 +428,13 @@ void TestRunner::appendLog(const QString& message) { _logFile.close(); autoTester->appendLogWindow(message); -} \ No newline at end of file +} + +Worker::Worker(const QString commandLine) { + _commandLine = commandLine; +} + +void Worker::process() { + system(_commandLine.toStdString().c_str()); + emit finished(); +} diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index e13b0be070..dfb670bb6d 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -15,11 +15,11 @@ #include #include #include -#include +#include #include #include -#include "Downloader.h" +class Worker; class TestRunner : public QObject { Q_OBJECT @@ -56,29 +56,27 @@ public: private slots: void checkTime(); + void installationComplete(); private: bool _automatedTestIsRunning{ false }; - QDir _appDataFolder; - QDir _savedAppDataFolder; - - QString _workingFolder; - QString _snapshotFolder; - - QString _installationFolder; - - Downloader* _downloader; - - const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; - const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; - const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; + QDir _appDataFolder; + QDir _savedAppDataFolder; + + QString _workingFolder; + QString _installationFolder; + QString _snapshotFolder; + + const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; + QString _branch; QString _user; @@ -92,6 +90,22 @@ private: QFile _logFile; QDateTime _testStartDateTime; + + QThread* thread; + Worker* worker; }; +class Worker : public QObject { + Q_OBJECT +public: + Worker(const QString commandLine); +public slots: + void process(); + +signals: + void finished(); + +private: + QString _commandLine; +}; #endif // hifi_testRunner_h \ No newline at end of file From 46b00535c808b098dbff9a9e1783984ee44f5730 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 13 Sep 2018 22:59:54 -0700 Subject: [PATCH 38/90] Running all night. --- tools/auto-tester/src/TestRunner.cpp | 60 +++++++++++++++++----------- tools/auto-tester/src/TestRunner.h | 8 +++- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 0d777fbecb..e135fdae45 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -33,11 +33,8 @@ TestRunner::TestRunner(std::vector dayCheckboxes, _timeEdits = timeEdits; _workingFolderLabel = workingFolderLabel; - thread = new QThread(); -} - -TestRunner::~TestRunner() { - disconnect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); + installerThread = new QThread(); + interfaceThread = new QThread(); } void TestRunner::setWorkingFolder() { @@ -95,11 +92,11 @@ void TestRunner::run() { } void TestRunner::installerDownloadComplete() { - appendLog(QString("Test started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + + appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + _testStartDateTime.date().toString("ddd, MMM d, yyyy")); - updateStatusLabel("Installing"); + updateStatusLabel("Installing"); // Kill any existing processes that would interfere with installation killProcesses(); @@ -118,30 +115,25 @@ void TestRunner::runInstaller() { QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); - worker = new Worker(commandLine); - worker->moveToThread(thread); - connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString))); - connect(thread, SIGNAL(started()), worker, SLOT(process())); + + worker->moveToThread(installerThread); + connect(installerThread, SIGNAL(started()), worker, SLOT(process())); connect(worker, SIGNAL(finished()), this, SLOT(installationComplete())); - connect(worker, SIGNAL(finished()), thread, SLOT(quit())); - connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - thread->start(); + installerThread->start(); } void TestRunner::installationComplete() { + disconnect(installerThread, SIGNAL(started()), worker, SLOT(process())); + disconnect(worker, SIGNAL(finished()), this, SLOT(installationComplete())); + delete worker; + createSnapshotFolder(); updateStatusLabel("Running tests"); startLocalServerProcesses(); runInterfaceWithTestScript(); - killProcesses(); - - evaluateResults(); - - // The High Fidelity AppData folder will be restored after evaluation has completed } void TestRunner::saveExistingHighFidelityAppDataFolder() { @@ -247,14 +239,29 @@ void TestRunner::startLocalServerProcesses() { } void TestRunner::runInterfaceWithTestScript() { -#ifdef Q_OS_WIN QString commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + _snapshotFolder; - system(commandLine.toStdString().c_str()); -#endif + worker = new Worker(commandLine); + + worker->moveToThread(interfaceThread); + connect(interfaceThread, SIGNAL(started()), worker, SLOT(process())); + connect(worker, SIGNAL(finished()), this, SLOT(interfaceExecutionComplete())); + interfaceThread->start(); +} + +void TestRunner::interfaceExecutionComplete() { + disconnect(interfaceThread, SIGNAL(started()), worker, SLOT(process())); + disconnect(worker, SIGNAL(finished()), this, SLOT(interfaceExecutionComplete())); + delete worker; + + killProcesses(); + + evaluateResults(); + + // The High Fidelity AppData folder will be restored after evaluation has completed } void TestRunner::evaluateResults() { @@ -267,6 +274,13 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { restoreHighFidelityAppDataFolder(); updateStatusLabel("Testing complete"); + + QDateTime currentDateTime = QDateTime::currentDateTime(); + + appendLog(QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" + + QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + currentDateTime.date().toString("ddd, MMM d, yyyy")); + _automatedTestIsRunning = false; } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index dfb670bb6d..abf4e87880 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -29,7 +29,6 @@ public: std::vector timeEdits, QLabel* workingFolderLabel, QObject* parent = 0); - ~TestRunner(); void setWorkingFolder(); @@ -42,9 +41,12 @@ public: void restoreHighFidelityAppDataFolder(); void createSnapshotFolder(); + void killProcesses(); void startLocalServerProcesses(); + void runInterfaceWithTestScript(); + void evaluateResults(); void automaticTestRunEvaluationComplete(QString zippedFolderName); void addBuildNumberToResults(QString zippedFolderName); @@ -57,6 +59,7 @@ public: private slots: void checkTime(); void installationComplete(); + void interfaceExecutionComplete(); private: bool _automatedTestIsRunning{ false }; @@ -91,7 +94,8 @@ private: QDateTime _testStartDateTime; - QThread* thread; + QThread* installerThread; + QThread* interfaceThread; Worker* worker; }; From f046d3b87dda1c42a9d5ac7ee38b7eb9bccf38d0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 17 Sep 2018 13:21:01 -0700 Subject: [PATCH 39/90] Delete allocated memory where needed. Added option to run server-less Added option to select build. --- tools/auto-tester/src/Test.cpp | 6 + tools/auto-tester/src/TestRailInterface.cpp | 9 +- tools/auto-tester/src/TestRunner.cpp | 147 +++++++++++++----- tools/auto-tester/src/TestRunner.h | 34 +++- tools/auto-tester/src/ui/AutoTester.cpp | 28 +++- tools/auto-tester/src/ui/AutoTester.h | 24 +-- tools/auto-tester/src/ui/AutoTester.ui | 92 +++++++++-- .../src/ui/TestRailRunSelectorWindow.cpp | 1 - 8 files changed, 271 insertions(+), 70 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index d3aa310d7d..aedf0be3d3 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -593,6 +593,12 @@ bool Test::createMDFile(const QString& directory) { } mdFile.close(); + + foreach (auto test, testScriptLines.stepList) { + delete test; + } + testScriptLines.stepList.clear(); + return true; } diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index e6f12100df..b2132cf85e 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -367,6 +367,7 @@ void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirect QProcess* process = new QProcess(); connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); @@ -491,7 +492,7 @@ void TestRailInterface::addRun() { ) { QProcess* process = new QProcess(); connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); - + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); @@ -499,6 +500,7 @@ void TestRailInterface::addRun() { process->start(_pythonCommand, parameters); } } + void TestRailInterface::updateRunWithResults() { QString filename = _outputDirectory + "/updateRunWithResults.py"; if (QFile::exists(filename)) { @@ -578,7 +580,7 @@ void TestRailInterface::updateRunWithResults() { ) { QProcess* process = new QProcess(); connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); - + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); @@ -753,6 +755,7 @@ void TestRailInterface::getReleasesFromTestRail() { QProcess* process = new QProcess(); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { updateReleasesComboData(exitCode, exitStatus); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); QStringList parameters = QStringList() << filename; process->start(_pythonCommand, parameters); @@ -1076,6 +1079,7 @@ void TestRailInterface::getTestSectionsFromTestRail() { QProcess* process = new QProcess(); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { updateSectionsComboData(exitCode, exitStatus); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); QStringList parameters = QStringList() << filename; process->start(_pythonCommand, parameters); @@ -1114,6 +1118,7 @@ void TestRailInterface::getRunsFromTestRail() { QProcess* process = new QProcess(); connect(process, static_cast(&QProcess::finished), this, [=](int exitCode, QProcess::ExitStatus exitStatus) { updateRunsComboData(exitCode, exitStatus); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); QStringList parameters = QStringList() << filename; diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index e135fdae45..624dce17eb 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -25,16 +25,44 @@ TestRunner::TestRunner(std::vector dayCheckboxes, std::vector timeEditCheckboxes, std::vector timeEdits, QLabel* workingFolderLabel, + QCheckBox* runServerless, + QCheckBox* runLatest, + QTextEdit* url, QObject* parent) : - QObject(parent) -{ + QObject(parent) { _dayCheckboxes = dayCheckboxes; _timeEditCheckboxes = timeEditCheckboxes; _timeEdits = timeEdits; _workingFolderLabel = workingFolderLabel; + _runServerless = runServerless; + _runLatest = runLatest; + _url = url; installerThread = new QThread(); + installerWorker = new Worker(); + installerWorker->moveToThread(installerThread); + installerThread->start(); + connect(this, SIGNAL(startInstaller()), installerWorker, SLOT(runCommand())); + connect(installerWorker, SIGNAL(commandComplete()), this, SLOT(installationComplete())); + interfaceThread = new QThread(); + interfaceWorker = new Worker(); + interfaceThread->start(); + interfaceWorker->moveToThread(interfaceThread); + connect(this, SIGNAL(startInterface()), interfaceWorker, SLOT(runCommand())); + connect(interfaceWorker, SIGNAL(commandComplete()), this, SLOT(interfaceExecutionComplete())); +} + +TestRunner::~TestRunner() { + delete installerThread; + delete interfaceThread; + + delete interfaceThread; + delete interfaceWorker; + + if (_timer) { + delete _timer; + } } void TestRunner::setWorkingFolder() { @@ -46,7 +74,7 @@ void TestRunner::setWorkingFolder() { } _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, - QFileDialog::ShowDirsOnly); + QFileDialog::ShowDirsOnly); // If user canceled then restore previous selection and return if (_workingFolder == "") { @@ -60,7 +88,6 @@ void TestRunner::setWorkingFolder() { autoTester->enableRunTabControls(); _workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); - // The time is checked every 30 seconds for automatic test start _timer = new QTimer(this); connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); _timer->start(30 * 1000); //time specified in ms @@ -79,10 +106,19 @@ void TestRunner::run() { // Download the latest High Fidelity installer and build XML. QStringList urls; - urls << INSTALLER_URL << BUILD_XML_URL; - QStringList filenames; - filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; + if (_runLatest->isChecked()) { + _installerFilename = INSTALLER_FILENAME_LATEST; + + urls << INSTALLER_URL_LATEST << BUILD_XML_URL; + filenames << _installerFilename << BUILD_XML_FILENAME; + } else { + QString urlText = _url->toPlainText(); + urls << urlText; + _installerFilename = getInstallerNameFromURL(urlText); + filenames << _installerFilename; + } + updateStatusLabel("Downloading installer"); @@ -110,29 +146,24 @@ void TestRunner::runInstaller() { QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; - QString installerFullPath = _workingFolder + "/" + INSTALLER_FILENAME; + QString installerFullPath = _workingFolder + "/" + _installerFilename; QString commandLine = QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); - worker = new Worker(commandLine); - - worker->moveToThread(installerThread); - connect(installerThread, SIGNAL(started()), worker, SLOT(process())); - connect(worker, SIGNAL(finished()), this, SLOT(installationComplete())); - installerThread->start(); + installerWorker->setCommandLine(commandLine); + emit startInstaller(); } void TestRunner::installationComplete() { - disconnect(installerThread, SIGNAL(started()), worker, SLOT(process())); - disconnect(worker, SIGNAL(finished()), this, SLOT(installationComplete())); - delete worker; - createSnapshotFolder(); updateStatusLabel("Running tests"); - startLocalServerProcesses(); + if (!_runServerless->isChecked()) { + startLocalServerProcesses(); + } + runInterfaceWithTestScript(); } @@ -239,24 +270,28 @@ void TestRunner::startLocalServerProcesses() { } void TestRunner::runInterfaceWithTestScript() { - QString commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + - "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + - "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + - _snapshotFolder; + QString commandLine; - worker = new Worker(commandLine); + if (_runServerless->isChecked()) { + // Move to an empty area + commandLine = + QString("\"") + QDir::toNativeSeparators(_installationFolder) + + "\\interface.exe\" --url hifi://localhost/9999,9999,9999/0.0,0.0,0.0,1.0 --testScript https://raw.githubusercontent.com/" + _user + + "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + + _snapshotFolder; + } else { + // There is no content, so no need to move + commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + + "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + + "/hifi_tests/" + _branch + + "/tests/content/entity/zone/testRecursive.js quitWhenFinished --testResultsLocation " + _snapshotFolder; + } - worker->moveToThread(interfaceThread); - connect(interfaceThread, SIGNAL(started()), worker, SLOT(process())); - connect(worker, SIGNAL(finished()), this, SLOT(interfaceExecutionComplete())); - interfaceThread->start(); + interfaceWorker->setCommandLine(commandLine); + emit startInterface(); } void TestRunner::interfaceExecutionComplete() { - disconnect(interfaceThread, SIGNAL(started()), worker, SLOT(process())); - disconnect(worker, SIGNAL(finished()), this, SLOT(interfaceExecutionComplete())); - delete worker; - killProcesses(); evaluateResults(); @@ -285,6 +320,13 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { } void TestRunner::addBuildNumberToResults(QString zippedFolderName) { + if (!_runLatest->isChecked()) { + QStringList filenameParts = zippedFolderName.split("."); + QString augmentedFilename = filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")." + filenameParts[1]; + QFile::rename(zippedFolderName, augmentedFilename); + + return; + } try { QDomDocument domDocument; QString filename{ _workingFolder + "/" + BUILD_XML_FILENAME }; @@ -444,11 +486,46 @@ void TestRunner::appendLog(const QString& message) { autoTester->appendLogWindow(message); } -Worker::Worker(const QString commandLine) { +QString TestRunner::getInstallerNameFromURL(const QString& url) { + // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe + try { + QStringList urlParts = url.split("/"); + int rr = urlParts.size(); + if (urlParts.size() != 8) { + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; + } + return urlParts[urlParts.size() - 1]; + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + +QString TestRunner::getPRNumberFromURL(const QString& url) { + try { + QStringList urlParts = url.split("/"); + QStringList filenameParts = urlParts[urlParts.size() - 1].split("-"); + if (filenameParts.size() != 5) { + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; + } + return filenameParts[3]; + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + +void Worker::setCommandLine(const QString& commandLine) { _commandLine = commandLine; } -void Worker::process() { +void Worker::runCommand() { system(_commandLine.toStdString().c_str()); - emit finished(); + emit commandComplete(); } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index abf4e87880..0843b438c8 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -28,8 +29,13 @@ public: std::vector timeEditCheckboxes, std::vector timeEdits, QLabel* workingFolderLabel, + QCheckBox* runServerless, + QCheckBox* runLatest, + QTextEdit* url, QObject* parent = 0); + ~TestRunner(); + void setWorkingFolder(); void run(); @@ -56,17 +62,26 @@ public: void updateStatusLabel(const QString& message); void appendLog(const QString& message); + QString getInstallerNameFromURL(const QString& url); + QString getPRNumberFromURL(const QString& url); + private slots: void checkTime(); void installationComplete(); void interfaceExecutionComplete(); +signals: + void startInstaller(); + void startInterface(); + private: bool _automatedTestIsRunning{ false }; - const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; - const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; + const QString INSTALLER_URL_LATEST{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; + const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" }; + QString _installerURL; + QString _installerFilename; const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; @@ -87,6 +102,9 @@ private: std::vector _timeEditCheckboxes; std::vector _timeEdits; QLabel* _workingFolderLabel; + QCheckBox* _runServerless; + QCheckBox* _runLatest; + QTextEdit* _url; QTimer* _timer; @@ -96,18 +114,22 @@ private: QThread* installerThread; QThread* interfaceThread; - Worker* worker; + Worker* installerWorker; + Worker* interfaceWorker; }; class Worker : public QObject { Q_OBJECT public: - Worker(const QString commandLine); + void setCommandLine(const QString& commandLine); + public slots: - void process(); + void runCommand(); signals: - void finished(); + void commandComplete(); + void startInstaller(); + void startInterface(); private: QString _commandLine; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 05eee2957a..3a438a5fb9 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -40,7 +40,22 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { //// _helpWindow.textBrowser->setText() } +AutoTester::~AutoTester() { + delete _signalMapper; + + if (_test) { + delete _test; + } + + if (_testRunner) { + delete _testRunner; + } +} + void AutoTester::setup() { + if (_test) { + delete _test; + } _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); std::vector dayCheckboxes; @@ -64,7 +79,10 @@ void AutoTester::setup() { timeEdits.emplace_back(_ui.timeEdit3); timeEdits.emplace_back(_ui.timeEdit4); - _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel); + if (_testRunner) { + delete _testRunner; + } + _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlTextEdit); } void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, @@ -144,6 +162,10 @@ void AutoTester::on_runNowButton_clicked() { _testRunner->run(); } +void AutoTester::on_checkBoxRunLatest_clicked() { + _ui.urlTextEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); +} + void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName) { _testRunner->automaticTestRunEvaluationComplete(zippedFolderName); } @@ -213,6 +235,10 @@ void AutoTester::downloadFiles(const QStringList& URLs, const QString& directory _ui.progressBar->setValue(0); _ui.progressBar->setVisible(true); + foreach (auto downloader, _downloaders) { + delete downloader; + } + _downloaders.clear(); for (int i = 0; i < _numberOfFilesToDownload; ++i) { downloadFile(URLs[i]); diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index de0622c670..f59d39e851 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -21,12 +21,12 @@ #include "HelpWindow.h" #include "../TestRunner.h" - class AutoTester : public QMainWindow { Q_OBJECT public: - AutoTester(QWidget *parent = Q_NULLPTR); + AutoTester(QWidget* parent = Q_NULLPTR); + ~AutoTester(); void setup(); @@ -39,7 +39,7 @@ public: void automaticTestRunEvaluationComplete(QString zippedFolderName); void downloadFile(const QUrl& url); - void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller); + void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller); void setUserText(const QString& user); QString getSelectedUser(); @@ -58,7 +58,7 @@ private slots: void on_evaluateTestsButton_clicked(); void on_createRecursiveScriptButton_clicked(); void on_createAllRecursiveScriptsButton_clicked(); - void on_createTestsButton_clicked(); + void on_createTestsButton_clicked(); void on_createMDFileButton_clicked(); void on_createAllMDFilesButton_clicked(); @@ -74,6 +74,8 @@ private slots: void on_setWorkingFolderButton_clicked(); void on_runNowButton_clicked(); + void on_checkBoxRunLatest_clicked(); + void on_updateTestRailRunResultsButton_clicked(); void on_hideTaskbarButton_clicked(); @@ -91,8 +93,8 @@ private slots: private: Ui::AutoTesterClass _ui; - Test* _test; - TestRunner* _testRunner; + Test* _test{ nullptr }; + TestRunner* _testRunner{ nullptr }; std::vector _downloaders; @@ -103,15 +105,15 @@ private: // Used to enable passing a parameter to slots QSignalMapper* _signalMapper; - int _numberOfFilesToDownload { 0 }; - int _numberOfFilesDownloaded { 0 }; - int _index { 0 }; + int _numberOfFilesToDownload{ 0 }; + int _numberOfFilesDownloaded{ 0 }; + int _index{ 0 }; - bool _isRunningFromCommandline { false }; + bool _isRunningFromCommandline{ false }; HelpWindow _helpWindow; void* _caller; }; -#endif // hifi_AutoTester_h \ No newline at end of file +#endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index fa9569a768..e241acc6f3 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 707 - 796 + 737 + 864 @@ -24,7 +24,7 @@ 470 - 660 + 750 100 40 @@ -36,10 +36,10 @@ - 30 + 40 140 631 - 501 + 581 @@ -196,7 +196,7 @@ 10 - 70 + 160 161 28 @@ -212,7 +212,7 @@ 20 - 150 + 240 91 241 @@ -319,7 +319,7 @@ 130 - 150 + 240 161 191 @@ -443,14 +443,14 @@ - ####### + (not set...) 300 - 120 + 210 311 331 @@ -460,7 +460,7 @@ 300 - 80 + 170 41 31 @@ -473,7 +473,7 @@ 350 - 80 + 170 271 31 @@ -482,6 +482,70 @@ ####### + + + + 20 + 70 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Serveless + + + true + + + + + + 20 + 100 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Latest + + + true + + + + + false + + + + 150 + 98 + 461 + 24 + + + + + + + 128 + 95 + 21 + 31 + + + + URL + + @@ -651,7 +715,7 @@ 80 - 670 + 760 255 23 @@ -666,7 +730,7 @@ 0 0 - 707 + 737 21 diff --git a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp index 54a3985a8b..2247fe33cc 100644 --- a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp +++ b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp @@ -19,7 +19,6 @@ TestRailRunSelectorWindow::TestRailRunSelectorWindow(QWidget *parent) { projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); } - void TestRailRunSelectorWindow::reset() { urlLineEdit->setDisabled(false); userLineEdit->setDisabled(false); From 0c3e3e977e9d27555070f4e3526740a130e8cf8e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 17 Sep 2018 16:06:11 -0700 Subject: [PATCH 40/90] Corrected pulling PR number from the executable name. --- tools/auto-tester/src/Test.cpp | 2 +- tools/auto-tester/src/TestRunner.cpp | 26 ++++++++++++-------------- tools/auto-tester/src/ui/AutoTester.ui | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index aedf0be3d3..9d77d4ed60 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -190,7 +190,7 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, _isRunningInAutomaticTestRun = isRunningInAutomaticTestRun; if (snapshotDirectory.isNull()) { - // Get list of JPEG images in folder, sorted by name + // Get list of PNG images in folder, sorted by name QString previousSelection = _snapshotDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 624dce17eb..71199782e9 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -270,23 +270,21 @@ void TestRunner::startLocalServerProcesses() { } void TestRunner::runInterfaceWithTestScript() { - QString commandLine; + QString exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + + "\\interface.exe\""; + QString url = QString("hifi://localhost"); if (_runServerless->isChecked()) { // Move to an empty area - commandLine = - QString("\"") + QDir::toNativeSeparators(_installationFolder) + - "\\interface.exe\" --url hifi://localhost/9999,9999,9999/0.0,0.0,0.0,1.0 --testScript https://raw.githubusercontent.com/" + _user + - "/hifi_tests/" + _branch + "/tests/testRecursive.js quitWhenFinished --testResultsLocation " + - _snapshotFolder; - } else { - // There is no content, so no need to move - commandLine = QString("\"") + QDir::toNativeSeparators(_installationFolder) + - "\\interface.exe\" --url hifi://localhost --testScript https://raw.githubusercontent.com/" + _user + - "/hifi_tests/" + _branch + - "/tests/content/entity/zone/testRecursive.js quitWhenFinished --testResultsLocation " + _snapshotFolder; + url = url + "/9999,9999,9999/0.0,0.0,0.0,1.0"; } + QString testScript = + QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; + + QString commandLine = exeFile + " --url " + url + " --testScript " + testScript + + " quitWhenFinished --testResultsLocation " + _snapshotFolder; + interfaceWorker->setCommandLine(commandLine); emit startInterface(); } @@ -508,10 +506,10 @@ QString TestRunner::getPRNumberFromURL(const QString& url) { try { QStringList urlParts = url.split("/"); QStringList filenameParts = urlParts[urlParts.size() - 1].split("-"); - if (filenameParts.size() != 5) { + if (filenameParts.size() <= 3) { throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; } - return filenameParts[3]; + return filenameParts[filenameParts.size() - 2]; } catch (QString errorMessage) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); exit(-1); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index e241acc6f3..5357a7e612 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -498,7 +498,7 @@ Serveless - true + false @@ -529,7 +529,7 @@ 150 98 461 - 24 + 28 From f872a95a7a9901900ecb730aaa814eea8473076b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 18 Sep 2018 08:49:11 -0700 Subject: [PATCH 41/90] Added number of failures to log. --- tools/auto-tester/src/Test.cpp | 16 ++++++------- tools/auto-tester/src/Test.h | 2 +- tools/auto-tester/src/TestRunner.cpp | 30 +++++++++++++++++-------- tools/auto-tester/src/TestRunner.h | 2 +- tools/auto-tester/src/ui/AutoTester.cpp | 4 ++-- tools/auto-tester/src/ui/AutoTester.h | 2 +- tools/auto-tester/src/ui/AutoTester.ui | 2 +- 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 9d77d4ed60..5e3dd50650 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -63,7 +63,7 @@ QString Test::zipAndDeleteTestResultsFolder() { return zippedResultsFileName; } -bool Test::compareImageLists() { +int Test::compareImageLists() { _progressBar->setMinimum(0); _progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); _progressBar->setValue(0); @@ -71,8 +71,8 @@ bool Test::compareImageLists() { // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - bool success{ true }; bool keepOn{ true }; + int numberOfFailures{ 0 }; for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) { // First check that images are the same size QImage resultImage(_resultImagesFullFilenames[i]); @@ -91,6 +91,7 @@ bool Test::compareImageLists() { } if (similarityIndex < THRESHOLD) { + ++numberOfFailures; TestFailure testFailure = TestFailure{ (float)similarityIndex, _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) @@ -102,7 +103,6 @@ bool Test::compareImageLists() { if (!isInteractiveMode) { appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); - success = false; } else { _mismatchWindow.exec(); @@ -111,11 +111,9 @@ bool Test::compareImageLists() { break; case USE_RESPONSE_FAIL: appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); - success = false; break; case USER_RESPONSE_ABORT: keepOn = false; - success = false; break; default: assert(false); @@ -128,7 +126,7 @@ bool Test::compareImageLists() { } _progressBar->setVisible(false); - return success; + return numberOfFailures; } void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { @@ -256,10 +254,10 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine, autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this); } void Test::finishTestsEvaluation() { - bool success = compareImageLists(); + int numberOfFailures = compareImageLists(); if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) { - if (success) { + if (numberOfFailures == 0) { QMessageBox::information(0, "Success", "All images are as expected"); } else { QMessageBox::information(0, "Failure", "One or more images are not as expected"); @@ -273,7 +271,7 @@ void Test::finishTestsEvaluation() { } if (_isRunningInAutomaticTestRun) { - autoTester->automaticTestRunEvaluationComplete(zippedFolderName); + autoTester->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); } } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index c6ef83e7e8..8ea00c909b 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -75,7 +75,7 @@ public: void createAllRecursiveScripts(); void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); - bool compareImageLists(); + int compareImageLists(); QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 71199782e9..89b9bf27d6 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -119,7 +119,6 @@ void TestRunner::run() { filenames << _installerFilename; } - updateStatusLabel("Downloading installer"); autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); @@ -174,7 +173,12 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; #endif - _appDataFolder = dataDirectory + "\\High Fidelity"; + if (!_runLatest->isChecked()) { + // We are running a PR build + _appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->toPlainText()); + } else { + _appDataFolder = dataDirectory + "\\High Fidelity"; + } if (_appDataFolder.exists()) { // The original folder is saved in a unique name @@ -270,8 +274,7 @@ void TestRunner::startLocalServerProcesses() { } void TestRunner::runInterfaceWithTestScript() { - QString exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + - "\\interface.exe\""; + QString exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; QString url = QString("hifi://localhost"); if (_runServerless->isChecked()) { @@ -302,7 +305,7 @@ void TestRunner::evaluateResults() { autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } -void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { +void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) { addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); @@ -310,9 +313,18 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder) { QDateTime currentDateTime = QDateTime::currentDateTime(); - appendLog(QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" + - QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - currentDateTime.date().toString("ddd, MMM d, yyyy")); + QString completionText = QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" + + QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + currentDateTime.date().toString("ddd, MMM d, yyyy"); + + if (numberOfFailures == 0) { + completionText += "; no failures"; + } else if (numberOfFailures == 1) { + completionText += "; 1 failure"; + } else { + completionText += QString("; ") + QString::number(numberOfFailures) + " failures"; + } + appendLog(completionText); _automatedTestIsRunning = false; } @@ -488,7 +500,7 @@ QString TestRunner::getInstallerNameFromURL(const QString& url) { // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe try { QStringList urlParts = url.split("/"); - int rr = urlParts.size(); + int rr = urlParts.size(); if (urlParts.size() != 8) { throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 0843b438c8..f54ae98e65 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -54,7 +54,7 @@ public: void runInterfaceWithTestScript(); void evaluateResults(); - void automaticTestRunEvaluationComplete(QString zippedFolderName); + void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); void addBuildNumberToResults(QString zippedFolderName); void copyFolder(const QString& source, const QString& destination); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 3a438a5fb9..502a447641 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -166,8 +166,8 @@ void AutoTester::on_checkBoxRunLatest_clicked() { _ui.urlTextEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); } -void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName) { - _testRunner->automaticTestRunEvaluationComplete(zippedFolderName); +void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) { + _testRunner->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); } void AutoTester::on_updateTestRailRunResultsButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index f59d39e851..939a03acf4 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -36,7 +36,7 @@ public: const QString& branch, const QString& user); - void automaticTestRunEvaluationComplete(QString zippedFolderName); + void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); void downloadFile(const QUrl& url); void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 5357a7e612..bfa9ca587e 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -495,7 +495,7 @@ <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> - Serveless + Server-less false From 69f5e9110a14cd8edac906312c8178b0656fa424 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 18 Sep 2018 10:16:42 -0700 Subject: [PATCH 42/90] Minor cleanup --- tools/auto-tester/src/TestRunner.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 89b9bf27d6..eca851a8c9 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -173,11 +173,11 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; #endif - if (!_runLatest->isChecked()) { + if (_runLatest->isChecked()) { + _appDataFolder = dataDirectory + "\\High Fidelity"; + } else { // We are running a PR build _appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->toPlainText()); - } else { - _appDataFolder = dataDirectory + "\\High Fidelity"; } if (_appDataFolder.exists()) { From 719176b931d690f35f3555fee4ae142ecefcff5b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 18 Sep 2018 12:01:13 -0700 Subject: [PATCH 43/90] Newer version. --- tools/auto-tester/AppDataHighFidelity/Interface.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/AppDataHighFidelity/Interface.json b/tools/auto-tester/AppDataHighFidelity/Interface.json index 429d6f109e..ca91a092c8 100644 --- a/tools/auto-tester/AppDataHighFidelity/Interface.json +++ b/tools/auto-tester/AppDataHighFidelity/Interface.json @@ -86,6 +86,8 @@ "Developer/Entities/Show Realtime Entity Stats": false, "Developer/Hands/Show Hand Targets": false, "Developer/Network/Disable Activity Logger": false, + "Developer/Network/Send wrong DS connect version": false, + "Developer/Network/Send wrong protocol version": false, "Developer/Physics/Highlight Simulation Ownership": false, "Developer/Physics/Show Bullet Bounding Boxes": false, "Developer/Physics/Show Bullet Collision": false, @@ -118,6 +120,7 @@ "Developer/Render/Temporal Antialiasing (FXAA if disabled)": true, "Developer/Render/Throttle FPS If Not Focus": true, "Developer/Render/World Axes": false, + "Developer/Show Animation Stats": false, "Developer/Show Overlays": true, "Developer/Show Statistics": false, "Developer/Timing/Log Extra Timing Details": false, @@ -174,6 +177,8 @@ "Maximum Texture Memory/8192 MB": false, "Maximum Texture Memory/Automatic Texture Memory": true, "Network/Disable Activity Logger": false, + "Network/Send wrong DS connect version": false, + "Network/Send wrong protocol version": false, "Perception Neuron/enabled": false, "Perception Neuron/serverAddress": "localhost", "Perception Neuron/serverPort": 7001, @@ -267,12 +272,14 @@ "hmdLODDecreaseFPS": 34, "io.highfidelity.attachPoints": "{}", "io.highfidelity.isEditing": false, - "sessionRunTime": 77, + "loginDialogPoppedUp": false, + "sessionRunTime": 42, "showLightsAndParticlesInEditMode": true, "showZonesInEditMode": true, "staticJitterBufferFrames": 1, "toolbar/com.highfidelity.interface.toolbar.system/desktopHeight": 1059, "toolbar/com.highfidelity.interface.toolbar.system/x": 655, "toolbar/com.highfidelity.interface.toolbar.system/y": 953, - "toolbar/constrainToolbarToCenterX": true + "toolbar/constrainToolbarToCenterX": true, + "wallet/autoLogout": true } From 8301b472feeaf857d4273f65eedb97957bc13755 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 18 Sep 2018 14:22:10 -0700 Subject: [PATCH 44/90] Automated login. --- .../AppDataHighFidelity/Interface/AccountInfo.bin | Bin 0 -> 489 bytes tools/auto-tester/src/TestRunner.cpp | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 tools/auto-tester/AppDataHighFidelity/Interface/AccountInfo.bin diff --git a/tools/auto-tester/AppDataHighFidelity/Interface/AccountInfo.bin b/tools/auto-tester/AppDataHighFidelity/Interface/AccountInfo.bin new file mode 100644 index 0000000000000000000000000000000000000000..65c971ea793926e8f7518d36c6079a59b4cc08e8 GIT binary patch literal 489 zcmZwDIZgvX6a>(RL`XzHz#(8ZGh;F&5@N{FEZx`=U}O(SWQ3fJvv3Dcw;T{q(O;|T ze;nZ2%53&x=hC@zPl8$RO|Z2SoLO0WuoA6Jf=6q!cV|W$PaH}(x$fuwHf-O*_G%nA zug~+%bKIoPhf&apCcROkQCf8hNg_vhkUa5_0l~HGDQko1ZiyTL-sL>kK znJMirv{|oHyD*6y#LuteZyXr}y=^`)c}q-5WyrjPP9q9hg-($@q>Wmg)jJm&efAoZ zd-b5o4i}jz`AWUYq}a!$LZQw-XjJ){#)wl_Ud=Y{-3fN)?CxIqvq%2$AEff1^k1Zz EU+;ia(EtDd literal 0 HcmV?d00001 diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index eca851a8c9..0417fd8d04 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -180,9 +180,13 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { _appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->toPlainText()); } + _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; + if (_savedAppDataFolder.exists()) { + _savedAppDataFolder.removeRecursively(); + } + if (_appDataFolder.exists()) { // The original folder is saved in a unique name - _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); } From 6c2b1271ab515ac0daa7cba0855aeb554bb8640b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 19 Sep 2018 12:34:27 -0700 Subject: [PATCH 45/90] Merge crap. --- tools/auto-tester/src/TestRunner.cpp | 86 ++++------------------------ tools/auto-tester/src/TestRunner.h | 34 +---------- 2 files changed, 14 insertions(+), 106 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 7eabe40c50..4d4d07a6d4 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -21,15 +21,6 @@ extern AutoTester* autoTester; #include #endif -static QLabel* _workingFolderLabel; -static QString _workingFolder; - -static const QString INSTALLER_URL{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; -static const QString INSTALLER_FILENAME{ "HighFidelity-Beta-latest-dev.exe" }; - -static const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; -static const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; - TestRunner::TestRunner(std::vector dayCheckboxes, std::vector timeEditCheckboxes, std::vector timeEdits, @@ -103,7 +94,7 @@ void TestRunner::setWorkingFolder() { } void TestRunner::run() { - runner->_testStartDateTime = QDateTime::currentDateTime(); + _testStartDateTime = QDateTime::currentDateTime(); _automatedTestIsRunning = true; // Initial setup @@ -113,9 +104,6 @@ void TestRunner::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); -<<<<<<< HEAD - QThread* thread = new QThread; -======= // Download the latest High Fidelity installer and build XML. QStringList urls; QStringList filenames; @@ -130,52 +118,25 @@ void TestRunner::run() { _installerFilename = getInstallerNameFromURL(urlText); filenames << _installerFilename; } ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 - Runner* runner = new Runner(); - runner->moveToThread(thread); - connect(runner, SIGNAL(error(QString)), this, SLOT(errorString(QString))); - connect(thread, SIGNAL(started()), runner, SLOT(process())); - connect(runner, SIGNAL(finished()), thread, SLOT(quit())); - connect(runner, SIGNAL(finished()), runner, SLOT(deleteLater())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - thread->start(); + updateStatusLabel("Downloading installer"); + + autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); + + // `installerDownloadComplete` will run after download has completed } void TestRunner::installerDownloadComplete() { -<<<<<<< HEAD - appendLog(QString("Test started at ") + QString::number(runner->_testStartDateTime.time().hour()) + ":" + - QString("%1").arg(runner->_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - runner->_testStartDateTime.date().toString("ddd, MMM d, yyyy")); - - autoTester->updateStatusLabel("Installing"); -======= appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + _testStartDateTime.date().toString("ddd, MMM d, yyyy")); updateStatusLabel("Installing"); ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 // Kill any existing processes that would interfere with installation killProcesses(); runInstaller(); -<<<<<<< HEAD - - createSnapshotFolder(); - - autoTester->updateStatusLabel("Running tests"); - - startLocalServerProcesses(); - runInterfaceWithTestScript(); - killProcesses(); - - evaluateResults(); - - // The High Fidelity AppData folder will be restored after evaluation has completed -======= ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 } void TestRunner::runInstaller() { @@ -344,7 +305,7 @@ void TestRunner::interfaceExecutionComplete() { } void TestRunner::evaluateResults() { - autoTester->updateStatusLabel("Evaluating results"); + updateStatusLabel("Evaluating results"); autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); } @@ -352,9 +313,6 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); -<<<<<<< HEAD - autoTester->updateStatusLabel("Testing complete"); -======= updateStatusLabel("Testing complete"); QDateTime currentDateTime = QDateTime::currentDateTime(); @@ -372,7 +330,6 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu } appendLog(completionText); ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 _automatedTestIsRunning = false; } @@ -525,6 +482,10 @@ void TestRunner::checkTime() { } } +void TestRunner::updateStatusLabel(const QString& message) { + autoTester->updateStatusLabel(message); +} + void TestRunner::appendLog(const QString& message) { if (!_logFile.open(QIODevice::Append | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), @@ -539,28 +500,6 @@ void TestRunner::appendLog(const QString& message) { autoTester->appendLogWindow(message); } -<<<<<<< HEAD -Runner::Runner() { -} - -Runner::~Runner() { -} - -void Runner::process() { - // Download the latest High Fidelity installer and build XML. - QStringList urls; - urls << INSTALLER_URL << BUILD_XML_URL; - - QStringList filenames; - filenames << INSTALLER_FILENAME << BUILD_XML_FILENAME; - - autoTester->updateStatusLabel("Downloading installer"); - - autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); - - // `installerDownloadComplete` will run after download has completed - emit finished(); -======= QString TestRunner::getInstallerNameFromURL(const QString& url) { // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe try { @@ -603,5 +542,4 @@ void Worker::setCommandLine(const QString& commandLine) { void Worker::runCommand() { system(_commandLine.toStdString().c_str()); emit commandComplete(); ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 -} +} \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 804b6ce826..6d3d00f78b 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -1,5 +1,5 @@ // -// Downloader.h +// TestRunner.h // // Created by Nissim Hadar on 1 Sept 2018. // Copyright 2013 High Fidelity, Inc. @@ -22,8 +22,6 @@ class Worker; -class Runner; - class TestRunner : public QObject { Q_OBJECT public: @@ -61,6 +59,7 @@ public: void copyFolder(const QString& source, const QString& destination); + void updateStatusLabel(const QString& message); void appendLog(const QString& message); QString getInstallerNameFromURL(const QString& url); @@ -89,12 +88,7 @@ private: QDir _appDataFolder; QDir _savedAppDataFolder; -<<<<<<< HEAD - QString _snapshotFolder; - -======= QString _workingFolder; ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 QString _installationFolder; QString _snapshotFolder; @@ -107,39 +101,15 @@ private: std::vector _dayCheckboxes; std::vector _timeEditCheckboxes; std::vector _timeEdits; -<<<<<<< HEAD -======= QLabel* _workingFolderLabel; QCheckBox* _runServerless; QCheckBox* _runLatest; QTextEdit* _url; ->>>>>>> 8301b472feeaf857d4273f65eedb97957bc13755 QTimer* _timer; QFile _logFile; - Runner* runner; -}; -class Runner : public QObject { - Q_OBJECT - -public: - friend TestRunner; - - Runner(); - ~Runner(); - - void updateStatusLabel(const QString& message); - -public slots: - void process(); - -signals: - void finished(); - void error(QString err); - -private: QDateTime _testStartDateTime; QThread* installerThread; From 2a1e3e93a5484ce6ccf5dcb7dddcfa471c5fa8c0 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 21 Sep 2018 14:52:32 -0700 Subject: [PATCH 46/90] Fixed a couple of bugs. --- tools/auto-tester/src/Test.cpp | 6 +++--- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 5e3dd50650..e67c5ab65a 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -489,7 +489,7 @@ bool Test::createAllFilesSetup() { parent += "/"; } - _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent, + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return @@ -569,8 +569,8 @@ bool Test::createMDFile(const QString& directory) { QString testName = testScriptLines.title; stream << "# " << testName << "\n"; - // Find the relevant part of the path to the test (i.e. from "tests" down - QString partialPath = extractPathFromTestsDown(_testDirectory); + // Find the relevant part of the path to the test (i.e. from "tests" down) + QString partialPath = extractPathFromTestsDown(_testsRootDirectory); stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 502a447641..fcc6cea8da 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -95,7 +95,7 @@ void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, } void AutoTester::on_tabWidget_currentChanged(int index) { - if (index == 1 || index == 2) { + if (index == 2 || index == 3) { _ui.userTextEdit->setDisabled(false); _ui.branchTextEdit->setDisabled(false); } else { From 6fe106089a3cd2845f43f640f6965269172f829e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 21 Sep 2018 17:34:37 -0700 Subject: [PATCH 47/90] Decreased threshold a wee bit. --- tools/auto-tester/src/Test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 8ea00c909b..40414e6c41 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -108,7 +108,7 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD{ 0.96 }; + const double THRESHOLD{ 0.95 }; QDir _imageDirectory; From 574be7614cb9032247dd0ba6084be453867e2e2b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 28 Sep 2018 15:34:22 -0700 Subject: [PATCH 48/90] Deal with blanks in path. --- tools/auto-tester/src/TestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 4d4d07a6d4..cdf8aa5604 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -148,7 +148,7 @@ void TestRunner::runInstaller() { QString installerFullPath = _workingFolder + "/" + _installerFilename; QString commandLine = - QDir::toNativeSeparators(installerFullPath) + " /S /D=" + QDir::toNativeSeparators(_installationFolder); + "\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder); installerWorker->setCommandLine(commandLine); emit startInstaller(); From 7d60c4c850e01d46bece751ee4c1f1dd7a5015f6 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 28 Sep 2018 15:35:09 -0700 Subject: [PATCH 49/90] Add HostName to Test Run name. --- tools/auto-tester/src/TestRailInterface.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index b2132cf85e..666c6a4a5c 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -46,7 +47,6 @@ QString TestRailInterface::getObject(const QString& path) { return path.right(path.length() - path.lastIndexOf("/") - 1); } - bool TestRailInterface::setPythonCommand() { if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); @@ -478,8 +478,8 @@ void TestRailInterface::addRun() { stream << "\tcase_ids.append(case['id'])\n\n"; // Now, we can create the run - stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + - "', 'suite_id': " + _suiteID + + stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + "[" + + QHostInfo::localHostName() + "]" + "', 'suite_id': " + _suiteID + ", 'include_all': False, 'case_ids': case_ids}\n"; stream << "run = client.send_post('add_run/" + _projectID + "', data)\n"; @@ -1028,7 +1028,6 @@ void TestRailInterface::processTestPython(const QString& fullDirectory, QString testContent = QString("Execute instructions in [THIS TEST](") + testMDName + ")"; QString testExpected = QString("Refer to the expected result in the linked description."); - stream << "data = {\n" << "\t'title': '" << title << "',\n" << "\t'template_id': 2,\n" From a0af0b4d2dfa7143bf257471b275b7b0ba0d15e1 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 28 Sep 2018 15:35:45 -0700 Subject: [PATCH 50/90] Add enabling/disabling of Suite ID --- tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp | 2 ++ tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp | 2 ++ tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp index 414e4fca79..505e04b33e 100644 --- a/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp +++ b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp @@ -25,6 +25,7 @@ void TestRailResultsSelectorWindow::reset() { userLineEdit->setDisabled(false); passwordLineEdit->setDisabled(false); projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); OKButton->setDisabled(true); @@ -37,6 +38,7 @@ void TestRailResultsSelectorWindow::on_acceptButton_clicked() { userLineEdit->setDisabled(true); passwordLineEdit->setDisabled(true); projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); OKButton->setDisabled(false); diff --git a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp index 2247fe33cc..ac3419d46f 100644 --- a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp +++ b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp @@ -24,6 +24,7 @@ void TestRailRunSelectorWindow::reset() { userLineEdit->setDisabled(false); passwordLineEdit->setDisabled(false); projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); OKButton->setDisabled(true); sectionsComboBox->setDisabled(true); @@ -34,6 +35,7 @@ void TestRailRunSelectorWindow::on_acceptButton_clicked() { userLineEdit->setDisabled(true); passwordLineEdit->setDisabled(true); projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); OKButton->setDisabled(false); sectionsComboBox->setDisabled(false); diff --git a/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp index abb873ea14..638fe71819 100644 --- a/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp +++ b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp @@ -25,6 +25,7 @@ void TestRailTestCasesSelectorWindow::reset() { userLineEdit->setDisabled(false); passwordLineEdit->setDisabled(false); projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); OKButton->setDisabled(true); @@ -37,6 +38,7 @@ void TestRailTestCasesSelectorWindow::on_acceptButton_clicked() { userLineEdit->setDisabled(true); passwordLineEdit->setDisabled(true); projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); OKButton->setDisabled(false); From dd8d651c69f83a7b5d382a4f9bd082011388b3b7 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 28 Sep 2018 15:36:52 -0700 Subject: [PATCH 51/90] Changed TestEdit fields to LineEdit. --- tools/auto-tester/src/ui/AutoTester.cpp | 18 ++++++----- tools/auto-tester/src/ui/AutoTester.ui | 40 ++++++++++++------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index fcc6cea8da..2764caf30c 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,6 +36,8 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); + setWindowTitle("Auto Tester - v4.5"); + // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() } @@ -96,11 +98,11 @@ void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, void AutoTester::on_tabWidget_currentChanged(int index) { if (index == 2 || index == 3) { - _ui.userTextEdit->setDisabled(false); - _ui.branchTextEdit->setDisabled(false); + _ui.userLineEdit->setDisabled(false); + _ui.branchLineEdit->setDisabled(false); } else { - _ui.userTextEdit->setDisabled(true); - _ui.branchTextEdit->setDisabled(true); + _ui.userLineEdit->setDisabled(true); + _ui.branchLineEdit->setDisabled(true); } } @@ -280,19 +282,19 @@ void AutoTester::content() { } void AutoTester::setUserText(const QString& user) { - _ui.userTextEdit->setText(user); + _ui.userLineEdit->setText(user); } QString AutoTester::getSelectedUser() { - return _ui.userTextEdit->toPlainText(); + return _ui.userLineEdit->text(); } void AutoTester::setBranchText(const QString& branch) { - _ui.branchTextEdit->setText(branch); + _ui.branchLineEdit->setText(branch); } QString AutoTester::getSelectedBranch() { - return _ui.branchTextEdit->toPlainText(); + return _ui.branchLineEdit->text(); } void AutoTester::updateStatusLabel(const QString& status) { diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index bfa9ca587e..655d5f2b83 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -673,26 +673,6 @@ GitHub Branch - - - - 250 - 75 - 140 - 24 - - - - - - - 250 - 37 - 140 - 24 - - - @@ -724,6 +704,26 @@ 24 + + + + 220 + 40 + 161 + 21 + + + + + + + 220 + 80 + 161 + 21 + + + From 632f6647ddb0715fd1e35ac83005680f98d36ee4 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 30 Sep 2018 17:11:01 -0700 Subject: [PATCH 52/90] Add UAC warning if install fails. --- tools/auto-tester/src/TestRunner.cpp | 17 +++++++++++++++++ tools/auto-tester/src/TestRunner.h | 1 + 2 files changed, 18 insertions(+) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index cdf8aa5604..8b40b15260 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -155,6 +155,8 @@ void TestRunner::runInstaller() { } void TestRunner::installationComplete() { + verifyInstallationSucceeded(); + createSnapshotFolder(); updateStatusLabel("Running tests"); @@ -166,6 +168,21 @@ void TestRunner::installationComplete() { runInterfaceWithTestScript(); } +void TestRunner::verifyInstallationSucceeded() { + // Exit if the executables are missing. + // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error +#ifdef Q_OS_WIN + QFileInfo interfacExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""); + QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\""); + QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\""); + + if (!interfacExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { + QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); + exit(-1); + } +#endif +} + void TestRunner::saveExistingHighFidelityAppDataFolder() { QString dataDirectory{ "NOT FOUND" }; diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 6d3d00f78b..2083053503 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -42,6 +42,7 @@ public: void installerDownloadComplete(); void runInstaller(); + void verifyInstallationSucceeded(); void saveExistingHighFidelityAppDataFolder(); void restoreHighFidelityAppDataFolder(); From 5ce02c23c72adfe09bc0e85f802f5a6d77b79e3f Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 30 Sep 2018 17:40:05 -0700 Subject: [PATCH 53/90] Add UAC warning if install fails. --- tools/auto-tester/src/TestRunner.cpp | 8 ++++---- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 8b40b15260..f795826f6b 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -172,11 +172,11 @@ void TestRunner::verifyInstallationSucceeded() { // Exit if the executables are missing. // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error #ifdef Q_OS_WIN - QFileInfo interfacExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""); - QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\""); - QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\""); + QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe"); + QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe"); + QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe"); - if (!interfacExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { + if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); exit(-1); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 2764caf30c..e08224359f 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v4.5"); + setWindowTitle("Auto Tester - v4.6"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 39d55a6612f8bb984101603fa9d7cae5355c3bac Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 1 Oct 2018 13:08:44 -0700 Subject: [PATCH 54/90] Use correct URL for "serverless" --- tools/auto-tester/src/TestRunner.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index f795826f6b..2d32b58d37 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -300,7 +300,9 @@ void TestRunner::runInterfaceWithTestScript() { QString url = QString("hifi://localhost"); if (_runServerless->isChecked()) { // Move to an empty area - url = url + "/9999,9999,9999/0.0,0.0,0.0,1.0"; + url = "file:///~serverless/tutorial.json";////"/9999,9999,9999/0.0,0.0,0.0,1.0"; + } else { + url = "hifi://localhost"; } QString testScript = From b0dee3a802015b00b5b6d714adf425dccd45a818 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 1 Oct 2018 13:09:15 -0700 Subject: [PATCH 55/90] Use correct URL for "serverless" --- tools/auto-tester/src/TestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 2d32b58d37..dce02f8341 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -300,7 +300,7 @@ void TestRunner::runInterfaceWithTestScript() { QString url = QString("hifi://localhost"); if (_runServerless->isChecked()) { // Move to an empty area - url = "file:///~serverless/tutorial.json";////"/9999,9999,9999/0.0,0.0,0.0,1.0"; + url = "file:///~serverless/tutorial.json"; } else { url = "hifi://localhost"; } From f863fabe6872fb6f1964000deb731bc21a617e03 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 1 Oct 2018 18:10:32 -0700 Subject: [PATCH 56/90] Fix for create MD file. --- tools/auto-tester/src/Test.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index e67c5ab65a..d77f91b77f 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -569,9 +569,6 @@ bool Test::createMDFile(const QString& directory) { QString testName = testScriptLines.title; stream << "# " << testName << "\n"; - // Find the relevant part of the path to the test (i.e. from "tests" down) - QString partialPath = extractPathFromTestsDown(_testsRootDirectory); - stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; stream << "## Preconditions" << "\n"; From 71197ca0d16a4dc09d2fa42b26c5d21bbfcfde39 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 2 Oct 2018 11:30:00 -0700 Subject: [PATCH 57/90] Use new format of installer. --- tools/auto-tester/src/TestRunner.cpp | 207 ++++++++++++++---------- tools/auto-tester/src/TestRunner.h | 19 ++- tools/auto-tester/src/ui/AutoTester.cpp | 4 +- 3 files changed, 140 insertions(+), 90 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index dce02f8341..fbb0b16ed5 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -104,39 +104,62 @@ void TestRunner::run() { // This will be restored at the end of the tests saveExistingHighFidelityAppDataFolder(); - // Download the latest High Fidelity installer and build XML. + // Download the latest High Fidelity build XML. QStringList urls; QStringList filenames; - if (_runLatest->isChecked()) { - _installerFilename = INSTALLER_FILENAME_LATEST; - urls << INSTALLER_URL_LATEST << BUILD_XML_URL; - filenames << _installerFilename << BUILD_XML_FILENAME; - } else { - QString urlText = _url->toPlainText(); - urls << urlText; - _installerFilename = getInstallerNameFromURL(urlText); - filenames << _installerFilename; - } + urls << DEV_BUILD_XML_URL; + filenames << DEV_BUILD_XML_FILENAME; - updateStatusLabel("Downloading installer"); + updateStatusLabel("Downloading Build XML"); + buildXMLDownloaded = false; autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); - // `installerDownloadComplete` will run after download has completed + // `downloadComplete` will run after download has completed } -void TestRunner::installerDownloadComplete() { - appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + - QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - _testStartDateTime.date().toString("ddd, MMM d, yyyy")); +void TestRunner::downloadComplete() { + if (!buildXMLDownloaded) { + // Download of Build XML has completed + buildXMLDownloaded = true; - updateStatusLabel("Installing"); + parseBuildInformation(); - // Kill any existing processes that would interfere with installation - killProcesses(); + // Download the High Fidelity installer + QStringList urls; + QStringList filenames; + if (_runLatest->isChecked()) { + _installerFilename = INSTALLER_FILENAME_LATEST; - runInstaller(); + urls << _buildInformation.url; + filenames << _installerFilename; + } else { + QString urlText = _url->toPlainText(); + urls << urlText; + _installerFilename = getInstallerNameFromURL(urlText); + filenames << _installerFilename; + } + + updateStatusLabel("Downloading installer"); + + autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this); + + // `downloadComplete` will run again after download has completed + + } else { + // Download of Installer has completed + appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + + QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + _testStartDateTime.date().toString("ddd, MMM d, yyyy")); + + updateStatusLabel("Installing"); + + // Kill any existing processes that would interfere with installation + killProcesses(); + + runInstaller(); + } } void TestRunner::runInstaller() { @@ -360,70 +383,10 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) { return; } - try { - QDomDocument domDocument; - QString filename{ _workingFolder + "/" + BUILD_XML_FILENAME }; - QFile file(filename); - if (!file.open(QIODevice::ReadOnly) || !domDocument.setContent(&file)) { - throw QString("Could not open " + filename); - } - QString platformOfInterest; -#ifdef Q_OS_WIN - platformOfInterest = "windows"; -#else if Q_OS_MAC - platformOfInterest = "mac"; -#endif - QDomElement element = domDocument.documentElement(); - - // Verify first element is "projects" - if (element.tagName() != "projects") { - throw("File seems to be in wrong format"); - } - - element = element.firstChild().toElement(); - if (element.tagName() != "project") { - throw("File seems to be in wrong format"); - } - - if (element.attribute("name") != "interface") { - throw("File is not from 'interface' build"); - } - - // Now loop over the platforms - while (!element.isNull()) { - element = element.firstChild().toElement(); - QString sdf = element.tagName(); - if (element.tagName() != "platform" || element.attribute("name") != platformOfInterest) { - continue; - } - - // Next element should be the build - element = element.firstChild().toElement(); - if (element.tagName() != "build") { - throw("File seems to be in wrong format"); - } - - // Next element should be the version - element = element.firstChild().toElement(); - if (element.tagName() != "version") { - throw("File seems to be in wrong format"); - } - - // Add the build number to the end of the filename - QString build = element.text(); - QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = filenameParts[0] + "(" + build + ")." + filenameParts[1]; - QFile::rename(zippedFolderName, augmentedFilename); - } - - } catch (QString errorMessage) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); - exit(-1); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); - exit(-1); - } + QStringList filenameParts = zippedFolderName.split("."); + QString augmentedFilename = filenameParts[0] + "(" + _buildInformation.build + ")." + filenameParts[1]; + QFile::rename(zippedFolderName, augmentedFilename); } void TestRunner::restoreHighFidelityAppDataFolder() { @@ -554,6 +517,82 @@ QString TestRunner::getPRNumberFromURL(const QString& url) { } } +void TestRunner::parseBuildInformation() { + try { + QDomDocument domDocument; + QString filename{ _workingFolder + "/" + DEV_BUILD_XML_FILENAME }; + QFile file(filename); + if (!file.open(QIODevice::ReadOnly) || !domDocument.setContent(&file)) { + throw QString("Could not open " + filename); + } + + QString platformOfInterest; +#ifdef Q_OS_WIN + platformOfInterest = "windows"; +#else if Q_OS_MAC + platformOfInterest = "mac"; +#endif + QDomElement element = domDocument.documentElement(); + + // Verify first element is "projects" + if (element.tagName() != "projects") { + throw("File seems to be in wrong format"); + } + + element = element.firstChild().toElement(); + if (element.tagName() != "project") { + throw("File seems to be in wrong format"); + } + + if (element.attribute("name") != "interface") { + throw("File is not from 'interface' build"); + } + + // Now loop over the platforms + while (!element.isNull()) { + element = element.firstChild().toElement(); + if (element.tagName() != "platform" || element.attribute("name") != platformOfInterest) { + continue; + } + + // Next element should be the build + element = element.firstChild().toElement(); + if (element.tagName() != "build") { + throw("File seems to be in wrong format"); + } + + // Next element should be the version + element = element.firstChild().toElement(); + if (element.tagName() != "version") { + throw("File seems to be in wrong format"); + } + + // Add the build number to the end of the filename + _buildInformation.build = element.text(); + + // First sibling should be stable_version + element = element.nextSibling().toElement(); + if (element.tagName() != "stable_version") { + throw("File seems to be in wrong format"); + } + + // Next sibling should be url + element = element.nextSibling().toElement(); + if (element.tagName() != "url") { + throw("File seems to be in wrong format"); + } + _buildInformation.url = element.text(); + } + + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + void Worker::setCommandLine(const QString& commandLine) { _commandLine = commandLine; } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 2083053503..56e5b12dba 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -20,6 +20,12 @@ #include #include +class BuildInformation { +public: + QString build; + QString url; +}; + class Worker; class TestRunner : public QObject { @@ -40,7 +46,7 @@ public: void run(); - void installerDownloadComplete(); + void downloadComplete(); void runInstaller(); void verifyInstallationSucceeded(); @@ -66,6 +72,8 @@ public: QString getInstallerNameFromURL(const QString& url); QString getPRNumberFromURL(const QString& url); + void parseBuildInformation(); + private slots: void checkTime(); void installationComplete(); @@ -78,13 +86,14 @@ signals: private: bool _automatedTestIsRunning{ false }; - const QString INSTALLER_URL_LATEST{ "http://builds.highfidelity.com/HighFidelity-Beta-latest-dev.exe" }; const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" }; QString _installerURL; QString _installerFilename; - const QString BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; - const QString BUILD_XML_FILENAME{ "dev-builds.xml" }; + const QString DEV_BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; + const QString DEV_BUILD_XML_FILENAME{ "dev-builds.xml" }; + + bool buildXMLDownloaded; QDir _appDataFolder; QDir _savedAppDataFolder; @@ -117,6 +126,8 @@ private: QThread* interfaceThread; Worker* installerWorker; Worker* interfaceWorker; + + BuildInformation _buildInformation; }; class Worker : public QObject { diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index e08224359f..f68f55afa2 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v4.6"); + setWindowTitle("Auto Tester - v5.0"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() @@ -266,7 +266,7 @@ void AutoTester::saveFile(int index) { if (_caller == _test) { _test->finishTestsEvaluation(); } else if (_caller == _testRunner) { - _testRunner->installerDownloadComplete(); + _testRunner->downloadComplete(); } } else { _ui.progressBar->setValue(_numberOfFilesDownloaded); From ce4233ba2186ec200eeb59253e141546c5f854ed Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 3 Oct 2018 16:42:02 -0700 Subject: [PATCH 58/90] Removed erroneous underscore --- tools/auto-tester/src/Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index d77f91b77f..26616d9857 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -153,7 +153,7 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFa stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' stream << "Expected image was " << testFailure._expectedImageFilename << endl; stream << "Actual image was " << testFailure._actualImageFilename << endl; - stream << "Similarity _index was " << testFailure._error << endl; + stream << "Similarity index was " << testFailure._error << endl; descriptionFile.close(); From 5c7b4338b670ec2de723f60a8235d1b920b2019a Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 3 Oct 2018 16:43:09 -0700 Subject: [PATCH 59/90] Change threshold from 0.95 to 0.935 --- tools/auto-tester/src/Test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 40414e6c41..4eb08b2af4 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -108,7 +108,7 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD{ 0.95 }; + const double THRESHOLD{ 0.935 }; QDir _imageDirectory; From 318e83e5003dcfb46b2e49d5ddffdfa8dc4f57d1 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 3 Oct 2018 16:45:01 -0700 Subject: [PATCH 60/90] Disable "RunNow" button while running tests Check if Interface test scripts ran to completion. --- tools/auto-tester/src/TestRunner.cpp | 26 +++++++++++++++++++++---- tools/auto-tester/src/TestRunner.h | 6 ++++-- tools/auto-tester/src/ui/AutoTester.cpp | 4 ++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index fbb0b16ed5..ec7c8eadf8 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -9,6 +9,7 @@ // #include "TestRunner.h" +#include #include #include #include @@ -28,6 +29,7 @@ TestRunner::TestRunner(std::vector dayCheckboxes, QCheckBox* runServerless, QCheckBox* runLatest, QTextEdit* url, + QPushButton* runNow, QObject* parent) : QObject(parent) { _dayCheckboxes = dayCheckboxes; @@ -37,6 +39,7 @@ TestRunner::TestRunner(std::vector dayCheckboxes, _runServerless = runServerless; _runLatest = runLatest; _url = url; + _runNow = runNow; installerThread = new QThread(); installerWorker = new Worker(); @@ -94,6 +97,8 @@ void TestRunner::setWorkingFolder() { } void TestRunner::run() { + _runNow->setEnabled(false); + _testStartDateTime = QDateTime::currentDateTime(); _automatedTestIsRunning = true; @@ -240,10 +245,12 @@ void TestRunner::createSnapshotFolder() { // Just delete all PNGs from the folder if it already exists if (QDir(_snapshotFolder).exists()) { // Note that we cannot use just a `png` filter, as the filenames include periods + // Also, delete any `jpg` and `txt` files + // The idea is to leave only previous zipped result folders QDirIterator it(_snapshotFolder.toStdString().c_str()); while (it.hasNext()) { QString filename = it.next(); - if (filename.right(4) == ".png") { + if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") { QFile::remove(filename); } } @@ -341,6 +348,13 @@ void TestRunner::runInterfaceWithTestScript() { void TestRunner::interfaceExecutionComplete() { killProcesses(); + QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt"); + if (!testCompleted.exists()) { + QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts"); + _runNow->setEnabled(true); + return; + } + evaluateResults(); // The High Fidelity AppData folder will be restored after evaluation has completed @@ -352,7 +366,7 @@ void TestRunner::evaluateResults() { } void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) { - addBuildNumberToResults(zippedFolder); + addBuildNumberAndHostnameToResults(zippedFolder); restoreHighFidelityAppDataFolder(); updateStatusLabel("Testing complete"); @@ -373,9 +387,11 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu appendLog(completionText); _automatedTestIsRunning = false; + + _runNow->setEnabled(true); } -void TestRunner::addBuildNumberToResults(QString zippedFolderName) { +void TestRunner::addBuildNumberAndHostnameToResults(QString zippedFolderName) { if (!_runLatest->isChecked()) { QStringList filenameParts = zippedFolderName.split("."); QString augmentedFilename = filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")." + filenameParts[1]; @@ -385,7 +401,9 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) { } QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = filenameParts[0] + "(" + _buildInformation.build + ")." + filenameParts[1]; + QString augmentedFilename = + filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + QFile::rename(zippedFolderName, augmentedFilename); } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 56e5b12dba..4add393cd6 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ public: QCheckBox* runServerless, QCheckBox* runLatest, QTextEdit* url, + QPushButton* runNow, QObject* parent = 0); ~TestRunner(); @@ -62,7 +64,7 @@ public: void evaluateResults(); void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); - void addBuildNumberToResults(QString zippedFolderName); + void addBuildNumberAndHostnameToResults(QString zippedFolderName); void copyFolder(const QString& source, const QString& destination); @@ -115,7 +117,7 @@ private: QCheckBox* _runServerless; QCheckBox* _runLatest; QTextEdit* _url; - + QPushButton* _runNow; QTimer* _timer; QFile _logFile; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index f68f55afa2..ed69cb6fdb 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v5.0"); + setWindowTitle("Auto Tester - v5.1"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() @@ -84,7 +84,7 @@ void AutoTester::setup() { if (_testRunner) { delete _testRunner; } - _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlTextEdit); + _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlTextEdit, _ui.runNowButton); } void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, From b962212ca0e78a4267a7f44f43f13d285b03125e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 3 Oct 2018 16:52:00 -0700 Subject: [PATCH 61/90] Enable repo fields in 'Create' --- tools/auto-tester/src/ui/AutoTester.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index ed69cb6fdb..92feec36e1 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -97,7 +97,7 @@ void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, } void AutoTester::on_tabWidget_currentChanged(int index) { - if (index == 2 || index == 3) { + if (index == 0 || index == 2 || index == 3) { _ui.userLineEdit->setDisabled(false); _ui.branchLineEdit->setDisabled(false); } else { @@ -274,7 +274,7 @@ void AutoTester::saveFile(int index) { } void AutoTester::about() { - QMessageBox::information(0, "About", QString("Built ") + __DATE__ + " : " + __TIME__); + QMessageBox::information(0, "About", QString("Built ") + __DATE__ + ", " + __TIME__); } void AutoTester::content() { From 2cb0b2ea8d14272b4ae5e29090a90310ec4c1ba5 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 3 Oct 2018 17:19:45 -0700 Subject: [PATCH 62/90] WIP - adding AWS interface. --- tools/auto-tester/src/ui/AutoTester.ui | 121 +++++++++++++++++-------- 1 file changed, 85 insertions(+), 36 deletions(-) diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 655d5f2b83..b0cb61b995 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 737 - 864 + 720 + 870 @@ -36,14 +36,14 @@ - 40 + 45 140 - 631 - 581 + 630 + 580 - 2 + 3 @@ -52,8 +52,8 @@ - 145 - 20 + 195 + 60 220 40 @@ -65,8 +65,8 @@ - 20 - 140 + 70 + 180 220 40 @@ -78,8 +78,8 @@ - 270 - 140 + 320 + 180 220 40 @@ -91,8 +91,8 @@ - 145 - 80 + 195 + 120 220 40 @@ -104,8 +104,8 @@ - 20 - 260 + 70 + 300 220 40 @@ -117,8 +117,8 @@ - 270 - 260 + 320 + 300 220 40 @@ -130,8 +130,8 @@ - 20 - 200 + 70 + 240 220 40 @@ -143,8 +143,8 @@ - 270 - 200 + 320 + 240 220 40 @@ -161,7 +161,7 @@ - 160 + 200 130 211 40 @@ -174,7 +174,7 @@ - 160 + 200 200 211 40 @@ -554,7 +554,7 @@ - 130 + 200 180 120 20 @@ -570,7 +570,7 @@ - 260 + 330 170 101 40 @@ -583,14 +583,14 @@ - TestRail + Web Interface - 210 + 130 230 - 161 + 160 40 @@ -601,7 +601,7 @@ - 110 + 60 110 95 20 @@ -617,9 +617,9 @@ - 210 + 130 170 - 161 + 160 40 @@ -630,9 +630,9 @@ - 210 + 130 110 - 161 + 160 40 @@ -643,7 +643,7 @@ - 110 + 60 130 95 20 @@ -653,6 +653,55 @@ XML + + + + 40 + 30 + 271 + 300 + + + + TestRail + + + + + + 370 + 30 + 210 + 300 + + + + Amazon Web Services + + + + false + + + + 25 + 80 + 160 + 40 + + + + Create Web Page + + + + groupBox + updateTestRailRunResultsButton + createPythonScriptRadioButton + createTestRailRunButton + createTestRailTestCasesButton + createXMLScriptRadioButton + groupBox_2 @@ -730,7 +779,7 @@ 0 0 - 737 + 720 21 From ed94ffa38414e6a6c3d12fd8b6967d49dccbc7d1 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 4 Oct 2018 08:54:34 -0700 Subject: [PATCH 63/90] Can extract filename after selecting zipped results and temporary folder. --- tools/auto-tester/src/Test.cpp | 16 ++++++++++++++++ tools/auto-tester/src/Test.h | 6 +++++- tools/auto-tester/src/TestRailInterface.cpp | 13 +++++++++++-- tools/auto-tester/src/TestRailInterface.h | 4 +++- tools/auto-tester/src/ui/AutoTester.cpp | 6 +++++- tools/auto-tester/src/ui/AutoTester.h | 5 +++++ tools/auto-tester/src/ui/AutoTester.ui | 4 ++-- 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 26616d9857..659a3e3158 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -1027,3 +1027,19 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) { void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { _testRailCreateMode = testRailCreateMode; } + +void Test::createWebPage() { + QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, + "Zipped Test Results (*.zip)"); + if (testResults.isNull()) { + return; + } + + QString tempDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", + nullptr, QFileDialog::ShowDirsOnly); + if (tempDirectory.isNull()) { + return; + } + + _awsInterface.createWebPageFromResults(testResults, tempDirectory); +} \ No newline at end of file diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 4eb08b2af4..10cb779379 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -16,6 +16,7 @@ #include #include +#include "AWSInterface.h" #include "ImageComparer.h" #include "ui/MismatchWindow.h" #include "TestRailInterface.h" @@ -97,6 +98,8 @@ public: void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); + void createWebPage(); + private: QProgressBar* _progressBar; QCheckBox* _checkBoxInteractiveMode; @@ -151,8 +154,9 @@ private: bool _exitWhenComplete{ false }; TestRailInterface _testRailInterface; - TestRailCreateMode _testRailCreateMode { PYTHON }; + + AWSInterface _awsInterface; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 666c6a4a5c..29ad0cbc88 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -530,7 +530,7 @@ void TestRailInterface::updateRunWithResults() { stream << "failed_tests = set()\n"; - stream << "for entry in listdir('" + _outputDirectory + "/" + tempName + "'):\n"; + stream << "for entry in listdir('" + _outputDirectory + "/" + TEMP_NAME + "'):\n"; stream << "\tparts = entry.split('--tests.')[1].split('.')\n"; stream << "\tfailed_test = parts[0]\n"; stream << "\tfor i in range(1, len(parts) - 1):\n"; @@ -1157,11 +1157,20 @@ void TestRailInterface::updateTestRailRunResults(const QString& testResults, con createTestRailDotPyScript(); // Extract test failures from zipped folder - QString tempSubDirectory = tempDirectory + "/" + tempName; + QString tempSubDirectory = tempDirectory + "/" + TEMP_NAME; QDir dir = tempSubDirectory; dir.mkdir(tempSubDirectory); JlCompress::extractDir(testResults, tempSubDirectory); // TestRail will be updated after the process initiated by getTestRunFromTestRail has completed getRunsFromTestRail(); + + dir.rmdir(tempSubDirectory); +} + +void TestRailInterface::extractTestFailuresFromZippedFolder(const QString& testResults, const QString& tempDirectory) { + QString tempSubDirectory = tempDirectory + "/" + TEMP_NAME; + QDir dir = tempSubDirectory; + dir.mkdir(tempSubDirectory); + JlCompress::extractDir(testResults, tempSubDirectory); } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRailInterface.h b/tools/auto-tester/src/TestRailInterface.h index 325fa9d643..bcb3ad5a55 100644 --- a/tools/auto-tester/src/TestRailInterface.h +++ b/tools/auto-tester/src/TestRailInterface.h @@ -89,6 +89,7 @@ public: void updateRunWithResults(); bool setPythonCommand(); + void extractTestFailuresFromZippedFolder(const QString& testResults, const QString& tempDirectory); private: // HighFidelity Interface project ID in TestRail @@ -111,6 +112,7 @@ private: QString _suiteID; QString _testDirectory; + QString _testResults; QString _outputDirectory; QString _userGitHub; QString _branchGitHub; @@ -126,7 +128,7 @@ private: QStringList _runNames; std::vector _runIDs; - QString tempName{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + QString TEMP_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; }; #endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 92feec36e1..aac5d1b39b 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v5.1"); + setWindowTitle("Auto Tester - v6.0"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() @@ -212,6 +212,10 @@ void AutoTester::on_createXMLScriptRadioButton_clicked() { _test->setTestRailCreateMode(XML); } +void AutoTester::on_createWebPagePushButton_clicked() { + _test->createWebPage(); +} + void AutoTester::downloadFile(const QUrl& url) { _downloaders.emplace_back(new Downloader(url, this)); connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map())); diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 939a03acf4..429a8b60e1 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -20,6 +20,7 @@ #include "HelpWindow.h" #include "../TestRunner.h" +#include "../AWSInterface.h" class AutoTester : public QMainWindow { Q_OBJECT @@ -84,6 +85,8 @@ private slots: void on_createPythonScriptRadioButton_clicked(); void on_createXMLScriptRadioButton_clicked(); + void on_createWebPagePushButton_clicked(); + void on_closeButton_clicked(); void saveFile(int index); @@ -96,6 +99,8 @@ private: Test* _test{ nullptr }; TestRunner* _testRunner{ nullptr }; + AWSInterface _awsInterface; + std::vector _downloaders; // local storage for parameters - folder to store downloaded files in, and a list of their names diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index b0cb61b995..956806e269 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -43,7 +43,7 @@ - 3 + 4 @@ -680,7 +680,7 @@ - false + true From d79d092dc8b361f55d5d2250f3b809e06ad0105b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 4 Oct 2018 16:31:38 -0700 Subject: [PATCH 64/90] Can write an HTML file created from zipped results --- tools/auto-tester/src/AWSInterface.cpp | 216 +++++++++++++++++++++++++ tools/auto-tester/src/AWSInterface.h | 44 +++++ tools/auto-tester/src/TestRunner.cpp | 26 ++- 3 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 tools/auto-tester/src/AWSInterface.cpp create mode 100644 tools/auto-tester/src/AWSInterface.h diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp new file mode 100644 index 0000000000..865ce5a3d4 --- /dev/null +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -0,0 +1,216 @@ +// +// AWSInterface.cpp +// +// Created by Nissim Hadar on 3 Oct 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "AWSInterface.h" + +#include +#include + +#include +#include + +AWSInterface::AWSInterface(QObject* parent) : + QObject(parent) { +} + +void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& tempDirectory) { + // Extract test failures from zipped folder + _tempDirectory = tempDirectory; + + QDir dir = _tempDirectory; + dir.mkdir(_tempDirectory); + JlCompress::extractDir(testResults, _tempDirectory); + + createHTMLFile(testResults, tempDirectory); +} + +void AWSInterface::createHTMLFile(const QString& testResults, const QString& tempDirectory) { + // For file named `D:/tt/snapshots/TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ].zip` + // - the HTML will be named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]` + QString resultsPath = tempDirectory + "/" + resultsFolder + "/"; + QDir().mkdir(resultsPath); + QStringList tokens = testResults.split('/'); + QString htmlFilename = resultsPath + tokens[tokens.length() - 1].split('.')[0] + ".html"; + + QFile file(htmlFilename); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create '" + htmlFilename + "'"); + exit(-1); + } + + QTextStream stream(&file); + + startHTMLpage(stream); + writeHead(stream); + writeBody(testResults, stream); + finishHTMLpage(stream); + + file.close(); +} + +void AWSInterface::startHTMLpage(QTextStream& stream) { + stream << "\n"; + stream << "\n"; +} + +void AWSInterface::writeHead(QTextStream& stream) { + stream << "\t" << "\n"; + stream << "\t" << "\t" << "\n"; + stream << "\t" << "\n"; +} + +void AWSInterface::writeBody(const QString& testResults, QTextStream& stream) { + stream << "\t" << "\n"; + writeTitle(testResults, stream); + writeTable(stream); + stream << "\t" << "\n"; +} + +void AWSInterface::finishHTMLpage(QTextStream& stream) { + stream << "\n"; +} + +void AWSInterface::writeTitle(const QString& testResults, QTextStream& stream) { + // Separate relevant components from the results name + // The expected format is as follows: `D:/tt/snapshots/TestResults--2018-10-04_11-09-41(PR14128)[DESKTOP-PMKNLSQ].zip` + QStringList tokens = testResults.split('/'); + + // date_buildorPR_hostName will be 2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ] + QString date_buildorPR_hostName = tokens[tokens.length() - 1].split("--")[1].split(".")[0]; + + QString buildorPR = date_buildorPR_hostName.split('(')[1].split(')')[0]; + QString hostName = date_buildorPR_hostName.split('[')[1].split(']')[0]; + + QStringList dateList = date_buildorPR_hostName.split('(')[0].split('_')[0].split('-'); + QString year = dateList[0]; + QString month = dateList[1]; + QString day = dateList[2]; + + QStringList timeList = date_buildorPR_hostName.split('(')[0].split('_')[1].split('-'); + QString hour = timeList[0]; + QString minute = timeList[1]; + QString second = timeList[2]; + + const QString months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + stream << "\t" << "\t" << "

Failures for "; + stream << months[month.toInt() - 1] << " " << day << ", " << year << ", "; + stream << hour << ":" << minute << ":" << second << ", "; + + if (buildorPR.left(2) == "PR") { + stream << "PR " << buildorPR.right(buildorPR.length() - 2) << ", "; + } else { + stream << "build " << buildorPR << ", "; + } + + stream << "run on " << hostName << "

\n"; +} + +void AWSInterface::writeTable(QTextStream& stream) { + QString previousTestName{ "" }; + + // Loop over all entries in directory. This is done in stages, as the names are not in the order of the tests + // The first stage reads the directory names into a list + // The second stage renames the tests by removing everything up to "--tests." + // The third stage renames the directories + // The fourth and lasts stage creates the HTML entries + + QStringList originalNames; + QDirIterator it1(_tempDirectory.toStdString().c_str()); + while (it1.hasNext()) { + QString nextDirectory = it1.next(); + + // Skip `.` and `..` directories + if (nextDirectory.right(1) == ".") { + continue; + } + + // Only process failure folders + if (!nextDirectory.contains("--tests.")) { + continue; + } + + originalNames.append(nextDirectory); + } + + QStringList newNames; + for (int i = 0; i < originalNames.length(); ++i) { + newNames.append(originalNames[i].split("--tests.")[1]); + } + + for (int i = 0; i < newNames.length(); ++i) { + QDir dir(originalNames[i]); + dir.rename(originalNames[i], _tempDirectory + "/" + resultsFolder + "/" + newNames[i]); + } + + QDirIterator it2((_tempDirectory + "/" + resultsFolder).toStdString().c_str()); + while (it2.hasNext()) { + QString nextDirectory = it2.next(); + + // Skip `.` and `..` directories, as well as the HTML directory + if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + resultsFolder + "/TestResults--")) { + continue; + } + + int splitIndex = nextDirectory.lastIndexOf("."); + QString testName = nextDirectory.left(splitIndex).replace(".", " / "); + QString testNumber = nextDirectory.right(nextDirectory.length() - (splitIndex + 1)); + + // The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table + if (testName != previousTestName) { + if (!previousTestName.isEmpty()) { + closeTable(stream); + } + + previousTestName = testName; + + stream << "\t\t

" << testName << "

\n"; + + openTable(stream); + } + + createEntry(testNumber.toInt(), nextDirectory, stream); + } + + closeTable(stream); +} + +void AWSInterface::openTable(QTextStream& stream) { + stream << "\t\t\n"; + stream << "\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\n"; +} + +void AWSInterface::closeTable(QTextStream& stream) { + stream << "\t\t

Test

Actual Image

Expected Image

Difference Image

\n"; +} + +void AWSInterface::createEntry(int index, const QString& testFailure, QTextStream& stream) { + stream << "\t\t\t\n"; + stream << "\t\t\t\t\

" << QString::number(index) << "

\n"; + + // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000` + // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000` + QStringList failureNameComponents = testFailure.split('/'); + QString failureName = failureNameComponents[failureNameComponents.length() - 1]; + + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\n"; +} \ No newline at end of file diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h new file mode 100644 index 0000000000..975ac03817 --- /dev/null +++ b/tools/auto-tester/src/AWSInterface.h @@ -0,0 +1,44 @@ +// +// AWSInterface.h +// +// Created by Nissim Hadar on 3 Oct 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AWSInterface_h +#define hifi_AWSInterface_h + +#include +#include + +class AWSInterface : public QObject { + Q_OBJECT +public: + explicit AWSInterface(QObject* parent = 0); + + void createWebPageFromResults(const QString& testResults, const QString& tempDirectory); + + void createHTMLFile(const QString& testResults, const QString& tempDirectory); + + void startHTMLpage(QTextStream& stream); + void writeHead(QTextStream& stream); + void writeBody(const QString& testResults, QTextStream& stream); + void finishHTMLpage(QTextStream& stream); + + void writeTitle(const QString& testResults, QTextStream& stream); + void writeTable(QTextStream& stream); + void openTable(QTextStream& stream); + void closeTable(QTextStream& stream); + + void createEntry(int index, const QString& testFailure, QTextStream& stream); + +private: + QString _tempDirectory; + + const QString resultsFolder{ "HTML" }; +}; + +#endif // hifi_AWSInterface_h \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index ec7c8eadf8..e790575ce5 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -110,6 +110,8 @@ void TestRunner::run() { saveExistingHighFidelityAppDataFolder(); // Download the latest High Fidelity build XML. + // Note that this is not needed for PR builds (or whenever `Run Latest` is unchecked) + // It is still downloaded, to simplify the flow QStringList urls; QStringList filenames; @@ -129,12 +131,12 @@ void TestRunner::downloadComplete() { // Download of Build XML has completed buildXMLDownloaded = true; - parseBuildInformation(); - // Download the High Fidelity installer QStringList urls; QStringList filenames; if (_runLatest->isChecked()) { + parseBuildInformation(); + _installerFilename = INSTALLER_FILENAME_LATEST; urls << _buildInformation.url; @@ -392,18 +394,16 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu } void TestRunner::addBuildNumberAndHostnameToResults(QString zippedFolderName) { + QString augmentedFilename; if (!_runLatest->isChecked()) { QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")." + filenameParts[1]; - QFile::rename(zippedFolderName, augmentedFilename); - - return; + augmentedFilename = + filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + } else { + QStringList filenameParts = zippedFolderName.split("."); + augmentedFilename = + filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; } - - QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = - filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; - QFile::rename(zippedFolderName, augmentedFilename); } @@ -504,10 +504,6 @@ QString TestRunner::getInstallerNameFromURL(const QString& url) { // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe try { QStringList urlParts = url.split("/"); - int rr = urlParts.size(); - if (urlParts.size() != 8) { - throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; - } return urlParts[urlParts.size() - 1]; } catch (QString errorMessage) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); From 19f41eac2bc6e2755a02e542b71f4d09f03d6de8 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 5 Oct 2018 08:58:14 -0700 Subject: [PATCH 65/90] Renamed HTML folder. Corrected names of tests. --- tools/auto-tester/src/AWSInterface.cpp | 22 ++++++++++++++-------- tools/auto-tester/src/AWSInterface.h | 3 +-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 865ce5a3d4..446148c610 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -32,8 +32,12 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, const QS void AWSInterface::createHTMLFile(const QString& testResults, const QString& tempDirectory) { // For file named `D:/tt/snapshots/TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ].zip` - // - the HTML will be named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]` - QString resultsPath = tempDirectory + "/" + resultsFolder + "/"; + // - the HTML will be in a folder named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]` + QStringList pathComponents = testResults.split('/'); + QString filename = pathComponents[pathComponents.length() - 1]; + _resultsFolder = filename.left(filename.length() - 4); + + QString resultsPath = tempDirectory + "/" + _resultsFolder + "/"; QDir().mkdir(resultsPath); QStringList tokens = testResults.split('/'); QString htmlFilename = resultsPath + tokens[tokens.length() - 1].split('.')[0] + ".html"; @@ -151,21 +155,23 @@ void AWSInterface::writeTable(QTextStream& stream) { for (int i = 0; i < newNames.length(); ++i) { QDir dir(originalNames[i]); - dir.rename(originalNames[i], _tempDirectory + "/" + resultsFolder + "/" + newNames[i]); + dir.rename(originalNames[i], _tempDirectory + "/" + _resultsFolder + "/" + newNames[i]); } - QDirIterator it2((_tempDirectory + "/" + resultsFolder).toStdString().c_str()); + QDirIterator it2((_tempDirectory + "/" + _resultsFolder).toStdString().c_str()); while (it2.hasNext()) { QString nextDirectory = it2.next(); // Skip `.` and `..` directories, as well as the HTML directory - if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + resultsFolder + "/TestResults--")) { + if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + _resultsFolder + "/TestResults--")) { continue; } - int splitIndex = nextDirectory.lastIndexOf("."); - QString testName = nextDirectory.left(splitIndex).replace(".", " / "); - QString testNumber = nextDirectory.right(nextDirectory.length() - (splitIndex + 1)); + QStringList pathComponents = nextDirectory.split('/'); + QString filename = pathComponents[pathComponents.length() - 1]; + int splitIndex = filename.lastIndexOf("."); + QString testName = filename.left(splitIndex).replace(".", " / "); + QString testNumber = filename.right(filename.length() - (splitIndex + 1)); // The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table if (testName != previousTestName) { diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index 975ac03817..79c98015b0 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -37,8 +37,7 @@ public: private: QString _tempDirectory; - - const QString resultsFolder{ "HTML" }; + QString _resultsFolder; }; #endif // hifi_AWSInterface_h \ No newline at end of file From fd7139c39f54fd39db573d706f5f8d1190fb4db7 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 5 Oct 2018 09:50:22 -0700 Subject: [PATCH 66/90] Puts images in separate folder. --- tools/auto-tester/src/AWSInterface.cpp | 18 ++++++++++-------- tools/auto-tester/src/AWSInterface.h | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 446148c610..f167a5a8ad 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -23,8 +23,7 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, const QS // Extract test failures from zipped folder _tempDirectory = tempDirectory; - QDir dir = _tempDirectory; - dir.mkdir(_tempDirectory); + QDir().mkdir(_tempDirectory); JlCompress::extractDir(testResults, _tempDirectory); createHTMLFile(testResults, tempDirectory); @@ -153,12 +152,15 @@ void AWSInterface::writeTable(QTextStream& stream) { newNames.append(originalNames[i].split("--tests.")[1]); } + QString htmlFolder{ _tempDirectory + "/" + _resultsFolder + "/" + FAILURE_FOLDER }; + QDir().mkdir(htmlFolder); + for (int i = 0; i < newNames.length(); ++i) { QDir dir(originalNames[i]); - dir.rename(originalNames[i], _tempDirectory + "/" + _resultsFolder + "/" + newNames[i]); + dir.rename(originalNames[i], htmlFolder + "/" + newNames[i]); } - QDirIterator it2((_tempDirectory + "/" + _resultsFolder).toStdString().c_str()); + QDirIterator it2((htmlFolder).toStdString().c_str()); while (it2.hasNext()) { QString nextDirectory = it2.next(); @@ -186,7 +188,7 @@ void AWSInterface::writeTable(QTextStream& stream) { openTable(stream); } - createEntry(testNumber.toInt(), nextDirectory, stream); + createEntry(testNumber.toInt(), filename, stream); } closeTable(stream); @@ -215,8 +217,8 @@ void AWSInterface::createEntry(int index, const QString& testFailure, QTextStrea QStringList failureNameComponents = testFailure.split('/'); QString failureName = failureNameComponents[failureNameComponents.length() - 1]; - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; stream << "\t\t\t\n"; } \ No newline at end of file diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index 79c98015b0..1e2f3be556 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -38,6 +38,8 @@ public: private: QString _tempDirectory; QString _resultsFolder; + + const QString FAILURE_FOLDER{ "failures" }; }; #endif // hifi_AWSInterface_h \ No newline at end of file From 2bcdb129c0598ca7cb4b0ec0e4b6f7eab7e7406d Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 5 Oct 2018 18:31:49 -0700 Subject: [PATCH 67/90] Can create correct Python script to write HTML to AWS. --- tools/auto-tester/README.md | 3 + tools/auto-tester/src/AWSInterface.cpp | 109 ++++++++++++++++++------- tools/auto-tester/src/AWSInterface.h | 18 ++-- 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/tools/auto-tester/README.md b/tools/auto-tester/README.md index 9463df4b43..afd5d933e7 100644 --- a/tools/auto-tester/README.md +++ b/tools/auto-tester/README.md @@ -25,6 +25,9 @@ Python 3 can be downloaded from: 3. Mac (**macOS 64-bit/32-bit installer** or **macOS 64-bit/32-bit installer**) After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. +### AWS interface +Install the latest release of Boto3 via pip: +>pip install boto3 # Create ![](./Create.PNG) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index f167a5a8ad..56ca00f73a 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -19,32 +19,38 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) { } -void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& tempDirectory) { - // Extract test failures from zipped folder - _tempDirectory = tempDirectory; +void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory) { + _testResults = testResults; + _workingDirectory = workingDirectory; - QDir().mkdir(_tempDirectory); - JlCompress::extractDir(testResults, _tempDirectory); + extractTestFailuresFromZippedFolder(); + createHTMLFile(); - createHTMLFile(testResults, tempDirectory); + if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "HTML has been created", "Do you want to update AWS?", QMessageBox::Yes | QMessageBox::No).exec()) { + updateAWS(); + } } -void AWSInterface::createHTMLFile(const QString& testResults, const QString& tempDirectory) { +void AWSInterface::extractTestFailuresFromZippedFolder() { + QDir().mkdir(_workingDirectory); + JlCompress::extractDir(_testResults, _workingDirectory); +} + +void AWSInterface::createHTMLFile() { // For file named `D:/tt/snapshots/TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ].zip` // - the HTML will be in a folder named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]` - QStringList pathComponents = testResults.split('/'); + QStringList pathComponents = _testResults.split('/'); QString filename = pathComponents[pathComponents.length() - 1]; _resultsFolder = filename.left(filename.length() - 4); - QString resultsPath = tempDirectory + "/" + _resultsFolder + "/"; + QString resultsPath = _workingDirectory + "/" + _resultsFolder + "/"; QDir().mkdir(resultsPath); - QStringList tokens = testResults.split('/'); - QString htmlFilename = resultsPath + tokens[tokens.length() - 1].split('.')[0] + ".html"; + _htmlFilename = resultsPath + HTML_FILENAME; - QFile file(htmlFilename); + QFile file(_htmlFilename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not create '" + htmlFilename + "'"); + "Could not create '" + _htmlFilename + "'"); exit(-1); } @@ -52,7 +58,7 @@ void AWSInterface::createHTMLFile(const QString& testResults, const QString& tem startHTMLpage(stream); writeHead(stream); - writeBody(testResults, stream); + writeBody(stream); finishHTMLpage(stream); file.close(); @@ -73,9 +79,9 @@ void AWSInterface::writeHead(QTextStream& stream) { stream << "\t" << "\n"; } -void AWSInterface::writeBody(const QString& testResults, QTextStream& stream) { +void AWSInterface::writeBody(QTextStream& stream) { stream << "\t" << "\n"; - writeTitle(testResults, stream); + writeTitle(stream); writeTable(stream); stream << "\t" << "\n"; } @@ -84,10 +90,10 @@ void AWSInterface::finishHTMLpage(QTextStream& stream) { stream << "\n"; } -void AWSInterface::writeTitle(const QString& testResults, QTextStream& stream) { +void AWSInterface::writeTitle(QTextStream& stream) { // Separate relevant components from the results name // The expected format is as follows: `D:/tt/snapshots/TestResults--2018-10-04_11-09-41(PR14128)[DESKTOP-PMKNLSQ].zip` - QStringList tokens = testResults.split('/'); + QStringList tokens = _testResults.split('/'); // date_buildorPR_hostName will be 2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ] QString date_buildorPR_hostName = tokens[tokens.length() - 1].split("--")[1].split(".")[0]; @@ -130,7 +136,7 @@ void AWSInterface::writeTable(QTextStream& stream) { // The fourth and lasts stage creates the HTML entries QStringList originalNames; - QDirIterator it1(_tempDirectory.toStdString().c_str()); + QDirIterator it1(_workingDirectory.toStdString().c_str()); while (it1.hasNext()) { QString nextDirectory = it1.next(); @@ -152,15 +158,14 @@ void AWSInterface::writeTable(QTextStream& stream) { newNames.append(originalNames[i].split("--tests.")[1]); } - QString htmlFolder{ _tempDirectory + "/" + _resultsFolder + "/" + FAILURE_FOLDER }; - QDir().mkdir(htmlFolder); + _htmlFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURE_FOLDER; + QDir().mkdir(_htmlFolder); for (int i = 0; i < newNames.length(); ++i) { - QDir dir(originalNames[i]); - dir.rename(originalNames[i], htmlFolder + "/" + newNames[i]); + QDir().rename(originalNames[i], _htmlFolder + "/" + newNames[i]); } - QDirIterator it2((htmlFolder).toStdString().c_str()); + QDirIterator it2((_htmlFolder).toStdString().c_str()); while (it2.hasNext()) { QString nextDirectory = it2.next(); @@ -210,15 +215,61 @@ void AWSInterface::closeTable(QTextStream& stream) { void AWSInterface::createEntry(int index, const QString& testFailure, QTextStream& stream) { stream << "\t\t\t\n"; - stream << "\t\t\t\t\

" << QString::number(index) << "

\n"; + stream << "\t\t\t\t

" << QString::number(index) << "

\n"; // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000` // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000` QStringList failureNameComponents = testFailure.split('/'); QString failureName = failureNameComponents[failureNameComponents.length() - 1]; - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; stream << "\t\t\t\n"; -} \ No newline at end of file +} + +void AWSInterface::updateAWS() { + QString filename = _workingDirectory + "/updateAWS.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'addTestCases.py'"); + exit(-1); + } + + QTextStream stream(&file); + + stream << "import boto3\n"; + stream << "s3 = boto3.resource('s3')\n\n"; + + QDirIterator it1(_htmlFolder.toStdString().c_str()); + while (it1.hasNext()) { + QString nextDirectory = it1.next(); + + // Skip `.` and `..` directories + if (nextDirectory.right(1) == ".") { + continue; + } + + // nextDirectory looks like `D:/t/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/failures/engine.render.effect.bloom.00000` + // We need to concatenate the last 3 components, to get `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/failures/engine.render.effect.bloom.00000` + QStringList parts = nextDirectory.split('/'); + QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1]; + + const QString imageNames[] { "Actual Image.png", "Expected Image.png", "Difference Image.png" }; + + for (int i = 0; i < 3; ++i) { + stream << "data = open('" << filename << "/" << imageNames[i] << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; + } + } + + stream << "data = open('" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data)\n"; + + file.close(); +} diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index 1e2f3be556..0da9eae838 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -19,27 +19,33 @@ class AWSInterface : public QObject { public: explicit AWSInterface(QObject* parent = 0); - void createWebPageFromResults(const QString& testResults, const QString& tempDirectory); - - void createHTMLFile(const QString& testResults, const QString& tempDirectory); + void createWebPageFromResults(const QString& testResults, const QString& workingDirectory); + void extractTestFailuresFromZippedFolder(); + void createHTMLFile(); void startHTMLpage(QTextStream& stream); void writeHead(QTextStream& stream); - void writeBody(const QString& testResults, QTextStream& stream); + void writeBody(QTextStream& stream); void finishHTMLpage(QTextStream& stream); - void writeTitle(const QString& testResults, QTextStream& stream); + void writeTitle(QTextStream& stream); void writeTable(QTextStream& stream); void openTable(QTextStream& stream); void closeTable(QTextStream& stream); void createEntry(int index, const QString& testFailure, QTextStream& stream); + void updateAWS(); + private: - QString _tempDirectory; + QString _testResults; + QString _workingDirectory; QString _resultsFolder; + QString _htmlFolder; + QString _htmlFilename; const QString FAILURE_FOLDER{ "failures" }; + const QString HTML_FILENAME{ "TestResults.html" }; }; #endif // hifi_AWSInterface_h \ No newline at end of file From fd6518ec56e7cc14759f920867c6f7490ce9f76d Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sat, 6 Oct 2018 16:23:47 -0700 Subject: [PATCH 68/90] Delete old failures before unzipping --- tools/auto-tester/src/AWSInterface.cpp | 26 +++++++++++++++-- tools/auto-tester/src/AWSInterface.h | 9 ++++++ tools/auto-tester/src/PythonInterface.cpp | 32 +++++++++++++++++++++ tools/auto-tester/src/PythonInterface.h | 26 +++++++++++++++++ tools/auto-tester/src/TestRailInterface.cpp | 32 ++------------------- tools/auto-tester/src/TestRailInterface.h | 16 +++++------ tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 7 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 tools/auto-tester/src/PythonInterface.cpp create mode 100644 tools/auto-tester/src/PythonInterface.h diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 56ca00f73a..5544a93727 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -11,12 +11,14 @@ #include #include +#include #include #include -AWSInterface::AWSInterface(QObject* parent) : - QObject(parent) { +AWSInterface::AWSInterface(QObject* parent) : QObject(parent) { + _pythonInterface = new PythonInterface(); + _pythonCommand = _pythonInterface->getPythonCommand(); } void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory) { @@ -32,6 +34,16 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, const QS } void AWSInterface::extractTestFailuresFromZippedFolder() { + // For a test results zip file called `D:/tt/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ].zip` + // the folder will be called `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]` + // and, this folder will be in the workign directory + QStringList parts =_testResults.split('/'); + QString zipFolderName = _workingDirectory + "/" + parts[parts.length() - 1].split('.')[0]; + if (QDir(zipFolderName).exists()) { + QDir dir = zipFolderName; + dir.removeRecursively(); + } + QDir().mkdir(_workingDirectory); JlCompress::extractDir(_testResults, _workingDirectory); } @@ -272,4 +284,14 @@ void AWSInterface::updateAWS() { stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data)\n"; file.close(); + + QProcess* process = new QProcess(); + + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + + QStringList parameters = QStringList() << filename ; + process->start(_pythonCommand, parameters); } diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index 0da9eae838..b642315a0a 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -14,6 +14,10 @@ #include #include +#include "ui/BusyWindow.h" + +#include "PythonInterface.h" + class AWSInterface : public QObject { Q_OBJECT public: @@ -46,6 +50,11 @@ private: const QString FAILURE_FOLDER{ "failures" }; const QString HTML_FILENAME{ "TestResults.html" }; + + BusyWindow _busyWindow; + + PythonInterface* _pythonInterface; + QString _pythonCommand; }; #endif // hifi_AWSInterface_h \ No newline at end of file diff --git a/tools/auto-tester/src/PythonInterface.cpp b/tools/auto-tester/src/PythonInterface.cpp new file mode 100644 index 0000000000..4922b8a8df --- /dev/null +++ b/tools/auto-tester/src/PythonInterface.cpp @@ -0,0 +1,32 @@ +// +// PythonInterface.cpp +// +// Created by Nissim Hadar on Oct 6, 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PythonInterface.h" + +#include +#include +#include + +PythonInterface::PythonInterface() { + if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { + QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); + if (!QFile::exists(_pythonPath + "/" + _pythonExe)) { + QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + _pythonPath); + } + _pythonCommand = _pythonPath + "/" + _pythonExe; + } else { + QMessageBox::critical(0, "PYTHON_PATH not defined", + "Please set PYTHON_PATH to directory containing the Python executable"); + exit(-1); + } +} + +QString PythonInterface::getPythonCommand() { + return _pythonCommand; +} diff --git a/tools/auto-tester/src/PythonInterface.h b/tools/auto-tester/src/PythonInterface.h new file mode 100644 index 0000000000..f32a39a644 --- /dev/null +++ b/tools/auto-tester/src/PythonInterface.h @@ -0,0 +1,26 @@ +// +// PythonInterface.h +// +// Created by Nissim Hadar on Oct 6, 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PythonInterface_h +#define hifi_PythonInterface_h + +#include + +class PythonInterface { +public: + PythonInterface(); + + QString getPythonCommand(); + +private: + const QString _pythonExe{ "python.exe" }; + QString _pythonCommand; +}; + +#endif // hifi_PythonInterface_h diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 29ad0cbc88..2127a53be6 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -41,29 +41,15 @@ TestRailInterface::TestRailInterface() { _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); + + _pythonInterface = new PythonInterface(); + _pythonCommand = _pythonInterface->getPythonCommand(); } QString TestRailInterface::getObject(const QString& path) { return path.right(path.length() - path.lastIndexOf("/") - 1); } -bool TestRailInterface::setPythonCommand() { - if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { - QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); - if (!QFile::exists(_pythonPath + "/" + _pythonExe)) { - QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + _pythonPath); - } - _pythonCommand = _pythonPath + "/" + _pythonExe; - return true; - } else { - QMessageBox::critical(0, "PYTHON_PATH not defined", - "Please set PYTHON_PATH to directory containing the Python executable"); - return false; - } - - return false; -} - // Creates the testrail.py script // This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-python void TestRailInterface::createTestRailDotPyScript() { @@ -770,10 +756,6 @@ void TestRailInterface::createTestSuitePython(const QString& testDirectory, _userGitHub = userGitHub; _branchGitHub = branchGitHub; - if (!setPythonCommand()) { - return; - } - if (!requestTestRailTestCasesDataFromUser()) { return; } @@ -1127,10 +1109,6 @@ void TestRailInterface::getRunsFromTestRail() { void TestRailInterface::createTestRailRun(const QString& outputDirectory) { _outputDirectory = outputDirectory; - if (!setPythonCommand()) { - return; - } - if (!requestTestRailRunDataFromUser()) { return; } @@ -1145,10 +1123,6 @@ void TestRailInterface::createTestRailRun(const QString& outputDirectory) { void TestRailInterface::updateTestRailRunResults(const QString& testResults, const QString& tempDirectory) { _outputDirectory = tempDirectory; - if (!setPythonCommand()) { - return; - } - if (!requestTestRailResultsDataFromUser()) { return; } diff --git a/tools/auto-tester/src/TestRailInterface.h b/tools/auto-tester/src/TestRailInterface.h index bcb3ad5a55..6843ca0142 100644 --- a/tools/auto-tester/src/TestRailInterface.h +++ b/tools/auto-tester/src/TestRailInterface.h @@ -12,7 +12,6 @@ #define hifi_test_testrail_interface_h #include "ui/BusyWindow.h" - #include "ui/TestRailTestCasesSelectorWindow.h" #include "ui/TestRailRunSelectorWindow.h" #include "ui/TestRailResultsSelectorWindow.h" @@ -22,7 +21,9 @@ #include #include -class TestRailInterface : public QObject{ +#include "PythonInterface.h" + +class TestRailInterface : public QObject { Q_OBJECT public: @@ -65,9 +66,7 @@ public: bool requestTestRailRunDataFromUser(); bool requestTestRailResultsDataFromUser(); - void createAddTestCasesPythonScript(const QString& testDirectory, - const QString& userGitHub, - const QString& branchGitHub); + void createAddTestCasesPythonScript(const QString& testDirectory, const QString& userGitHub, const QString& branchGitHub); void processDirectoryPython(const QString& directory, QTextStream& stream, @@ -88,7 +87,6 @@ public: void addRun(); void updateRunWithResults(); - bool setPythonCommand(); void extractTestFailuresFromZippedFolder(const QString& testResults, const QString& tempDirectory); private: @@ -117,9 +115,6 @@ private: QString _userGitHub; QString _branchGitHub; - const QString _pythonExe{ "python.exe" }; - QString _pythonCommand; - QStringList _releaseNames; QStringList _sectionNames; @@ -129,6 +124,9 @@ private: std::vector _runIDs; QString TEMP_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + + PythonInterface* _pythonInterface; + QString _pythonCommand; }; #endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index aac5d1b39b..1cfe872662 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.0"); + setWindowTitle("Auto Tester - v6.1"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 138ca76d1c3aa5f4dcd77ad8d21873a3d54a5e55 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Sun, 7 Oct 2018 18:36:29 -0700 Subject: [PATCH 69/90] Now works. --- tools/auto-tester/src/AWSInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 5544a93727..28d6e8dcfa 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -275,12 +275,12 @@ void AWSInterface::updateAWS() { const QString imageNames[] { "Actual Image.png", "Expected Image.png", "Difference Image.png" }; for (int i = 0; i < 3; ++i) { - stream << "data = open('" << filename << "/" << imageNames[i] << "', 'rb')\n"; + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << imageNames[i] << "', 'rb')\n"; stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; } } - stream << "data = open('" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; + stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data)\n"; file.close(); From 054494677b55b397c98fe1f5891c9a0143f46117 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 07:46:18 -0700 Subject: [PATCH 70/90] Added checkbox to automatically update AWS. --- tools/auto-tester/src/AWSInterface.cpp | 6 +++-- tools/auto-tester/src/AWSInterface.h | 3 ++- tools/auto-tester/src/Test.cpp | 4 +-- tools/auto-tester/src/Test.h | 2 +- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- tools/auto-tester/src/ui/AutoTester.ui | 33 +++++++++++++++++-------- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 28d6e8dcfa..51b7efcc15 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -21,14 +21,16 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) { _pythonCommand = _pythonInterface->getPythonCommand(); } -void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory) { +void AWSInterface::createWebPageFromResults(const QString& testResults, + const QString& workingDirectory, + QCheckBox* updateAWSCheckBox) { _testResults = testResults; _workingDirectory = workingDirectory; extractTestFailuresFromZippedFolder(); createHTMLFile(); - if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "HTML has been created", "Do you want to update AWS?", QMessageBox::Yes | QMessageBox::No).exec()) { + if (updateAWSCheckBox->isChecked()) { updateAWS(); } } diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index b642315a0a..ebfc027345 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -11,6 +11,7 @@ #ifndef hifi_AWSInterface_h #define hifi_AWSInterface_h +#include #include #include @@ -23,7 +24,7 @@ class AWSInterface : public QObject { public: explicit AWSInterface(QObject* parent = 0); - void createWebPageFromResults(const QString& testResults, const QString& workingDirectory); + void createWebPageFromResults(const QString& testResults, const QString& workingDirectory, QCheckBox* updateAWSCheckBox); void extractTestFailuresFromZippedFolder(); void createHTMLFile(); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 659a3e3158..a150805bd2 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -1028,7 +1028,7 @@ void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { _testRailCreateMode = testRailCreateMode; } -void Test::createWebPage() { +void Test::createWebPage(QCheckBox* updateAWSCheckBox) { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, "Zipped Test Results (*.zip)"); if (testResults.isNull()) { @@ -1041,5 +1041,5 @@ void Test::createWebPage() { return; } - _awsInterface.createWebPageFromResults(testResults, tempDirectory); + _awsInterface.createWebPageFromResults(testResults, tempDirectory, updateAWSCheckBox); } \ No newline at end of file diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 10cb779379..93e8ec249d 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -98,7 +98,7 @@ public: void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); - void createWebPage(); + void createWebPage(QCheckBox* updateAWSCheckBox); private: QProgressBar* _progressBar; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 1cfe872662..1450d657cb 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -213,7 +213,7 @@ void AutoTester::on_createXMLScriptRadioButton_clicked() { } void AutoTester::on_createWebPagePushButton_clicked() { - _test->createWebPage(); + _test->createWebPage(_ui.updateAWSCheckBox); } void AutoTester::downloadFile(const QUrl& url) { diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 956806e269..e551302f00 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -588,7 +588,7 @@ - 130 + 90 230 160 40 @@ -601,7 +601,7 @@ - 60 + 20 110 95 20 @@ -617,7 +617,7 @@ - 130 + 90 170 160 40 @@ -630,7 +630,7 @@ - 130 + 90 110 160 40 @@ -643,7 +643,7 @@ - 60 + 20 130 95 20 @@ -656,9 +656,9 @@ - 40 + -20 30 - 271 + 291 300 @@ -669,9 +669,9 @@ - 370 + 329 30 - 210 + 291 300 @@ -684,7 +684,7 @@ - 25 + 110 80 160 40 @@ -694,6 +694,19 @@ Create Web Page + + + + 20 + 92 + 81 + 17 + + + + Update AWS + + groupBox updateTestRailRunResultsButton From 21c917b503c6fa2b89a80c9916300d2ba93650a3 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 08:05:58 -0700 Subject: [PATCH 71/90] Updated README --- tools/auto-tester/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/README.md b/tools/auto-tester/README.md index afd5d933e7..5bff09d1be 100644 --- a/tools/auto-tester/README.md +++ b/tools/auto-tester/README.md @@ -26,7 +26,15 @@ Python 3 can be downloaded from: After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. ### AWS interface -Install the latest release of Boto3 via pip: +#### Windows +1. Download the AWS CLI from `https://aws.amazon.com/cli/` +1. Install (installer is named `AWSCLI64PY3.msi`) +1. Open a new command prompt and run `aws configure` +1. Enter the AWS account number +1. Enter the secret key +1. Leave region name and ouput format as default [None] + +1. Install the latest release of Boto3 via pip: >pip install boto3 # Create ![](./Create.PNG) From 179c2fa01dfe0b344f596a58a8c430af3def88a1 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 12:40:07 -0700 Subject: [PATCH 72/90] Set the HTML content type. --- tools/auto-tester/src/AWSInterface.cpp | 2 +- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 51b7efcc15..6587ee9c61 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -283,7 +283,7 @@ void AWSInterface::updateAWS() { } stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data)\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; file.close(); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 1450d657cb..4fba5f46c3 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.1"); + setWindowTitle("Auto Tester - v6.2"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 443e15ae81cb38d26158ddfcf91ae641f7ad7f4a Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 12:45:08 -0700 Subject: [PATCH 73/90] Copy SSL DLLs to the Release folder. --- tools/auto-tester/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index 7705dc4361..06930ab0fe 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -55,5 +55,12 @@ if (WIN32) POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" ) + + # add a custom command to copy the SSL DLLs + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory D:/GitHub/vcpkg/installed/x64-windows/bin "$" + ) endif () \ No newline at end of file From 92b26fec370ab50ee0c5d84ece010478dc4d2b1c Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 13:42:21 -0700 Subject: [PATCH 74/90] Uploads to he hifi-qa bucket. --- tools/auto-tester/src/AWSInterface.cpp | 4 ++-- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 6587ee9c61..b5c73d7534 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -278,12 +278,12 @@ void AWSInterface::updateAWS() { for (int i = 0; i < 3; ++i) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" << imageNames[i] << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-qa', Key='" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; } } stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-content', Key='nissim/" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-qa', Key='" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; file.close(); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 4fba5f46c3..1d2cb9551e 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.2"); + setWindowTitle("Auto Tester - v6.3"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From a5dc985308cd904d78fcf3b27f6baca0e80d036f Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 14:32:10 -0700 Subject: [PATCH 75/90] Shows URL of page on AWS. --- tools/auto-tester/src/AWSInterface.cpp | 16 +++++- tools/auto-tester/src/AWSInterface.h | 11 +++- tools/auto-tester/src/Test.cpp | 4 +- tools/auto-tester/src/Test.h | 2 +- tools/auto-tester/src/TestRunner.cpp | 8 +-- tools/auto-tester/src/TestRunner.h | 6 +- tools/auto-tester/src/ui/AutoTester.cpp | 6 +- tools/auto-tester/src/ui/AutoTester.ui | 73 ++++++++++++++----------- 8 files changed, 76 insertions(+), 50 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index b5c73d7534..9fdebb1f2a 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -23,9 +23,13 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) { void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory, - QCheckBox* updateAWSCheckBox) { + QCheckBox* updateAWSCheckBox, + QLineEdit* urlLineEdit) { _testResults = testResults; _workingDirectory = workingDirectory; + + _urlLineEdit = urlLineEdit; + _urlLineEdit->setEnabled(false); extractTestFailuresFromZippedFolder(); createHTMLFile(); @@ -278,15 +282,21 @@ void AWSInterface::updateAWS() { for (int i = 0; i < 3; ++i) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" << imageNames[i] << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-qa', Key='" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; } } stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='hifi-qa', Key='" << _resultsFolder << "/" << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << _resultsFolder << "/" + << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; file.close(); + // Show user the URL + _urlLineEdit->setEnabled(true); + _urlLineEdit->setText(QString("https://") + AWS_BUCKET + ".s3.amazonaws.com/" + _resultsFolder + "/" + HTML_FILENAME); + _urlLineEdit->setCursorPosition(0); + QProcess* process = new QProcess(); connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index ebfc027345..b0e23066a2 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -12,6 +12,7 @@ #define hifi_AWSInterface_h #include +#include #include #include @@ -24,7 +25,11 @@ class AWSInterface : public QObject { public: explicit AWSInterface(QObject* parent = 0); - void createWebPageFromResults(const QString& testResults, const QString& workingDirectory, QCheckBox* updateAWSCheckBox); + void createWebPageFromResults(const QString& testResults, + const QString& workingDirectory, + QCheckBox* updateAWSCheckBox, + QLineEdit* urlLineEdit); + void extractTestFailuresFromZippedFolder(); void createHTMLFile(); @@ -56,6 +61,10 @@ private: PythonInterface* _pythonInterface; QString _pythonCommand; + + QString AWS_BUCKET{ "hifi-qa" }; + + QLineEdit* _urlLineEdit; }; #endif // hifi_AWSInterface_h \ No newline at end of file diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index a150805bd2..a9682b1536 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -1028,7 +1028,7 @@ void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { _testRailCreateMode = testRailCreateMode; } -void Test::createWebPage(QCheckBox* updateAWSCheckBox) { +void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, "Zipped Test Results (*.zip)"); if (testResults.isNull()) { @@ -1041,5 +1041,5 @@ void Test::createWebPage(QCheckBox* updateAWSCheckBox) { return; } - _awsInterface.createWebPageFromResults(testResults, tempDirectory, updateAWSCheckBox); + _awsInterface.createWebPageFromResults(testResults, tempDirectory, updateAWSCheckBox, urlLineEdit); } \ No newline at end of file diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 93e8ec249d..28bc7ccad0 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -98,7 +98,7 @@ public: void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); - void createWebPage(QCheckBox* updateAWSCheckBox); + void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit); private: QProgressBar* _progressBar; diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index e790575ce5..756923828f 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -28,7 +28,7 @@ TestRunner::TestRunner(std::vector dayCheckboxes, QLabel* workingFolderLabel, QCheckBox* runServerless, QCheckBox* runLatest, - QTextEdit* url, + QLineEdit* url, QPushButton* runNow, QObject* parent) : QObject(parent) { @@ -142,7 +142,7 @@ void TestRunner::downloadComplete() { urls << _buildInformation.url; filenames << _installerFilename; } else { - QString urlText = _url->toPlainText(); + QString urlText = _url->text(); urls << urlText; _installerFilename = getInstallerNameFromURL(urlText); filenames << _installerFilename; @@ -224,7 +224,7 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() { _appDataFolder = dataDirectory + "\\High Fidelity"; } else { // We are running a PR build - _appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->toPlainText()); + _appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->text()); } _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; @@ -398,7 +398,7 @@ void TestRunner::addBuildNumberAndHostnameToResults(QString zippedFolderName) { if (!_runLatest->isChecked()) { QStringList filenameParts = zippedFolderName.split("."); augmentedFilename = - filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + filenameParts[0] + "(" + getPRNumberFromURL(_url->text()) + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; } else { QStringList filenameParts = zippedFolderName.split("."); augmentedFilename = diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 4add393cd6..dfa93b7b1c 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -14,9 +14,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -38,7 +38,7 @@ public: QLabel* workingFolderLabel, QCheckBox* runServerless, QCheckBox* runLatest, - QTextEdit* url, + QLineEdit* url, QPushButton* runNow, QObject* parent = 0); @@ -116,7 +116,7 @@ private: QLabel* _workingFolderLabel; QCheckBox* _runServerless; QCheckBox* _runLatest; - QTextEdit* _url; + QLineEdit* _url; QPushButton* _runNow; QTimer* _timer; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 1d2cb9551e..667a2f65d6 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -84,7 +84,7 @@ void AutoTester::setup() { if (_testRunner) { delete _testRunner; } - _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlTextEdit, _ui.runNowButton); + _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlLineEdit, _ui.runNowButton); } void AutoTester::startTestsEvaluation(const bool isRunningFromCommandLine, @@ -165,7 +165,7 @@ void AutoTester::on_runNowButton_clicked() { } void AutoTester::on_checkBoxRunLatest_clicked() { - _ui.urlTextEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); + _ui.urlLineEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); } void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) { @@ -213,7 +213,7 @@ void AutoTester::on_createXMLScriptRadioButton_clicked() { } void AutoTester::on_createWebPagePushButton_clicked() { - _test->createWebPage(_ui.updateAWSCheckBox); + _test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); } void AutoTester::downloadFile(const QUrl& url) { diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index e551302f00..51170fcd42 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -520,19 +520,6 @@ true - - - false - - - - 150 - 98 - 461 - 28 - - - @@ -546,6 +533,16 @@ URL + + + + 160 + 100 + 451 + 21 + + + @@ -588,8 +585,8 @@ - 90 - 230 + 240 + 220 160 40 @@ -601,8 +598,8 @@ - 20 - 110 + 170 + 100 95 20 @@ -617,8 +614,8 @@ - 90 - 170 + 240 + 160 160 40 @@ -630,8 +627,8 @@ - 90 - 110 + 240 + 100 160 40 @@ -643,8 +640,8 @@ - 20 - 130 + 170 + 120 95 20 @@ -656,9 +653,9 @@ - -20 + 10 30 - 291 + 601 300 @@ -669,10 +666,10 @@ - 329 - 30 - 291 - 300 + 10 + 350 + 601 + 151 @@ -684,8 +681,8 @@ - 110 - 80 + 240 + 30 160 40 @@ -697,8 +694,8 @@ - 20 - 92 + 150 + 42 81 17 @@ -707,6 +704,16 @@ Update AWS + + + + 20 + 90 + 561 + 21 + + + groupBox updateTestRailRunResultsButton From 71334cc8964869f3e48c104e19ca8bac11d5af04 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Mon, 8 Oct 2018 16:24:15 -0700 Subject: [PATCH 76/90] Deal correctly with zero failures. --- tools/auto-tester/src/TestRailInterface.cpp | 15 +++++++++------ tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 2127a53be6..ad25798b77 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -516,13 +516,16 @@ void TestRailInterface::updateRunWithResults() { stream << "failed_tests = set()\n"; - stream << "for entry in listdir('" + _outputDirectory + "/" + TEMP_NAME + "'):\n"; - stream << "\tparts = entry.split('--tests.')[1].split('.')\n"; - stream << "\tfailed_test = parts[0]\n"; - stream << "\tfor i in range(1, len(parts) - 1):\n"; - stream << "\t\tfailed_test = failed_test + '/' + parts[i]\n"; + QDir dir(_outputDirectory + "/" + TEMP_NAME); + if (dir.exists()) { + stream << "for entry in listdir('" + _outputDirectory + "/" + TEMP_NAME + "'):\n"; + stream << "\tparts = entry.split('--tests.')[1].split('.')\n"; + stream << "\tfailed_test = parts[0]\n"; + stream << "\tfor i in range(1, len(parts) - 1):\n"; + stream << "\t\tfailed_test = failed_test + '/' + parts[i]\n"; - stream << "\tfailed_tests.add(failed_test)\n\n"; + stream << "\tfailed_tests.add(failed_test)\n\n"; + } // Initialize the array of results that will be eventually used to update TestRail stream << "status_ids = []\n"; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 667a2f65d6..12eb3c8333 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.3"); + setWindowTitle("Auto Tester - v6.4"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 3ebf8ad293ecb6cc6708df9282a0196167ebc9df Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 10:44:19 -0700 Subject: [PATCH 77/90] Set version to 7.0 --- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 12eb3c8333..d49f3aaa1c 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.4"); + setWindowTitle("Auto Tester - v7.0"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 25b3a8c75c78b36c8162567158abb99a58b54657 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 12:02:42 -0700 Subject: [PATCH 78/90] Fixed gcc warnings. --- tools/auto-tester/src/TestRunner.cpp | 14 +++++--------- tools/auto-tester/src/TestRunner.h | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 756923828f..72e5e8f997 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -463,13 +463,8 @@ void TestRunner::checkTime() { // Check the time bool timeToRun{ false }; QTime time = now.time(); - int h = time.hour(); - int m = time.minute(); - for (int i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { - bool is = _timeEditCheckboxes[i]->isChecked(); - int hh = _timeEdits[i]->time().hour(); - int mm = _timeEdits[i]->time().minute(); + for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) && (_timeEdits[i]->time().minute() == now.time().minute())) { timeToRun = true; @@ -543,7 +538,7 @@ void TestRunner::parseBuildInformation() { QString platformOfInterest; #ifdef Q_OS_WIN platformOfInterest = "windows"; -#else if Q_OS_MAC +#elif Q_OS_MAC platformOfInterest = "mac"; #endif QDomElement element = domDocument.documentElement(); @@ -611,7 +606,8 @@ void Worker::setCommandLine(const QString& commandLine) { _commandLine = commandLine; } -void Worker::runCommand() { - system(_commandLine.toStdString().c_str()); +int Worker::runCommand() { + int result = system(_commandLine.toStdString().c_str()); emit commandComplete(); + return result; } \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index dfa93b7b1c..925ff6077a 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -138,7 +138,7 @@ public: void setCommandLine(const QString& commandLine); public slots: - void runCommand(); + int runCommand(); signals: void commandComplete(); From 956ede1fbb6ca6c3c01ffaf3f6c3cb8de1b21a4b Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 14:12:15 -0700 Subject: [PATCH 79/90] Update the run description with a link to AWS. --- tools/auto-tester/src/TestRailInterface.cpp | 9 ++++++++- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index ad25798b77..11bc5db4a4 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -559,7 +559,13 @@ void TestRailInterface::updateRunWithResults() { stream << "\tresults.append({'case_id': case_ids[i], 'status_id': status_ids[i] })\n\n"; stream << "data = { 'results': results }\n"; - stream << "section = client.send_post('add_results_for_cases/' + str(" << runID << "), data)\n"; + stream << "client.send_post('add_results_for_cases/' + str(" << runID << "), data)\n"; + + // Also update the run + QStringList parts = _testResults.split('/'); + QString resultName = parts[parts.length() - 1].split('.')[0]; + stream << "client.send_post('update_run/' + str(" << runID << ")," + << " { 'description' : 'https://hifi-qa.s3.amazonaws.com/" << resultName << "/TestResults.html' })\n"; file.close(); @@ -1125,6 +1131,7 @@ void TestRailInterface::createTestRailRun(const QString& outputDirectory) { void TestRailInterface::updateTestRailRunResults(const QString& testResults, const QString& tempDirectory) { _outputDirectory = tempDirectory; + _testResults = testResults; if (!requestTestRailResultsDataFromUser()) { return; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index d49f3aaa1c..4b3762a3b0 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v7.0"); + setWindowTitle("Auto Tester - v6.5"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From 35bf0fc75bc3c03fb360a6fc3d9ef70fe508f369 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 14:44:43 -0700 Subject: [PATCH 80/90] Fixed gcc warning. --- tools/auto-tester/src/TestRunner.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 72e5e8f997..419dcd2d30 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -462,7 +462,6 @@ void TestRunner::checkTime() { // Check the time bool timeToRun{ false }; - QTime time = now.time(); for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) && From 9b816321af1ac616a32b8bd593fa8ef782762c60 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 15:46:51 -0700 Subject: [PATCH 81/90] Corrected tab order for GitHub repository. --- tools/auto-tester/src/ui/AutoTester.ui | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 51170fcd42..c94d6b3885 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -845,6 +845,53 @@ + + userLineEdit + branchLineEdit + createTestsButton + createMDFileButton + createAllMDFilesButton + createTestsOutlineButton + createRecursiveScriptButton + createAllRecursiveScriptsButton + createTestAutoScriptButton + createAllTestAutoScriptsButton + hideTaskbarButton + showTaskbarButton + runNowButton + sundayCheckBox + wednesdayCheckBox + tuesdayCheckBox + thursdayCheckBox + fridayCheckBox + saturdayCheckBox + mondayCheckBox + timeEdit1 + timeEdit2 + timeEdit3 + timeEdit4 + timeEdit1checkBox + timeEdit2checkBox + timeEdit3checkBox + timeEdit4checkBox + setWorkingFolderButton + plainTextEdit + checkBoxServerless + checkBoxRunLatest + urlLineEdit + checkBoxInteractiveMode + evaluateTestsButton + updateTestRailRunResultsButton + createPythonScriptRadioButton + createTestRailRunButton + createTestRailTestCasesButton + createXMLScriptRadioButton + createWebPagePushButton + updateAWSCheckBox + awsURLLineEdit + closeButton + tabWidget + From 697e678612e835ec841a7843a07e5c81f261f431 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 16:54:38 -0700 Subject: [PATCH 82/90] TesftFailure (etal.) changed to TestResult. Successes are also added to the zipped results folder. --- tools/auto-tester/src/AWSInterface.cpp | 4 +- tools/auto-tester/src/AWSInterface.h | 2 +- tools/auto-tester/src/Test.cpp | 66 +++++++++++++-------- tools/auto-tester/src/Test.h | 5 +- tools/auto-tester/src/common.h | 4 +- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- tools/auto-tester/src/ui/MismatchWindow.cpp | 18 +++--- tools/auto-tester/src/ui/MismatchWindow.h | 2 +- 8 files changed, 60 insertions(+), 43 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index 9fdebb1f2a..a87a8beb41 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -231,13 +231,13 @@ void AWSInterface::closeTable(QTextStream& stream) { stream << "\t\t\n"; } -void AWSInterface::createEntry(int index, const QString& testFailure, QTextStream& stream) { +void AWSInterface::createEntry(int index, const QString& testResult, QTextStream& stream) { stream << "\t\t\t\n"; stream << "\t\t\t\t

" << QString::number(index) << "

\n"; // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000` // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000` - QStringList failureNameComponents = testFailure.split('/'); + QStringList failureNameComponents = testResult.split('/'); QString failureName = failureNameComponents[failureNameComponents.length() - 1]; stream << "\t\t\t\t\n"; diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index b0e23066a2..08df326000 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -43,7 +43,7 @@ public: void openTable(QTextStream& stream); void closeTable(QTextStream& stream); - void createEntry(int index, const QString& testFailure, QTextStream& stream); + void createEntry(int index, const QString& testResult, QTextStream& stream); void updateAWS(); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index a9682b1536..0e787d1dbf 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -58,7 +58,8 @@ QString Test::zipAndDeleteTestResultsFolder() { //In all cases, for the next evaluation _testResultsFolderPath = ""; - _index = 1; + _failureIndex = 1; + _successIndex = 1; return zippedResultsFileName; } @@ -90,19 +91,20 @@ int Test::compareImageLists() { similarityIndex = _imageComparer.compareImages(resultImage, expectedImage); } + TestResult testResult = TestResult{ + (float)similarityIndex, + _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image + }; + if (similarityIndex < THRESHOLD) { ++numberOfFailures; - TestFailure testFailure = TestFailure{ - (float)similarityIndex, - _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) - QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image - }; - _mismatchWindow.setTestFailure(testFailure); + _mismatchWindow.setTestResult(testResult); if (!isInteractiveMode) { - appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); + appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true); } else { _mismatchWindow.exec(); @@ -110,7 +112,7 @@ int Test::compareImageLists() { case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: - appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); + appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true); break; case USER_RESPONSE_ABORT: keepOn = false; @@ -120,6 +122,8 @@ int Test::compareImageLists() { break; } } + } else { + appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), false); } _progressBar->setValue(i); @@ -129,20 +133,32 @@ int Test::compareImageLists() { return numberOfFailures; } -void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { +void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestResult testResult, QPixmap comparisonImage, bool hasFailed) { if (!QDir().exists(_testResultsFolderPath)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found"); exit(-1); } - QString failureFolderPath { _testResultsFolderPath + "/Failure_" + QString::number(_index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) }; - if (!QDir().mkdir(failureFolderPath)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath); + QString resultFolderPath; + if (hasFailed) { + resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" + + testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); + + ++_failureIndex; + } else { + resultFolderPath = _testResultsFolderPath + "/Success_" + QString::number(_successIndex) + "--" + + testResult._actualImageFilename.left(testResult._actualImageFilename.length() - 4); + + ++_successIndex; + } + + if (!QDir().mkdir(resultFolderPath)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to create folder " + resultFolderPath); exit(-1); } - ++_index; - QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); + QFile descriptionFile(resultFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME); exit(-1); @@ -150,10 +166,10 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFa // Create text file describing the failure QTextStream stream(&descriptionFile); - stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' - stream << "Expected image was " << testFailure._expectedImageFilename << endl; - stream << "Actual image was " << testFailure._actualImageFilename << endl; - stream << "Similarity index was " << testFailure._error << endl; + stream << "Test failed in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/' + stream << "Expected image was " << testResult._expectedImageFilename << endl; + stream << "Actual image was " << testResult._actualImageFilename << endl; + stream << "Similarity index was " << testResult._error << endl; descriptionFile.close(); @@ -161,21 +177,21 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFa QString sourceFile; QString destinationFile; - sourceFile = testFailure._pathname + testFailure._expectedImageFilename; - destinationFile = failureFolderPath + "/" + "Expected Image.png"; + sourceFile = testResult._pathname + testResult._expectedImageFilename; + destinationFile = resultFolderPath + "/" + "Expected Image.png"; if (!QFile::copy(sourceFile, destinationFile)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } - sourceFile = testFailure._pathname + testFailure._actualImageFilename; - destinationFile = failureFolderPath + "/" + "Actual Image.png"; + sourceFile = testResult._pathname + testResult._actualImageFilename; + destinationFile = resultFolderPath + "/" + "Actual Image.png"; if (!QFile::copy(sourceFile, destinationFile)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } - comparisonImage.save(failureFolderPath + "/" + "Difference Image.png"); + comparisonImage.save(resultFolderPath + "/" + "Difference Image.png"); } void Test::startTestsEvaluation(const bool isRunningFromCommandLine, diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 28bc7ccad0..f653a91782 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -84,7 +84,7 @@ public: void includeTest(QTextStream& textStream, const QString& testPathname); - void appendTestResultsToFile(const QString& testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); + void appendTestResultsToFile(const QString& testResultsFolderPath, TestResult testResult, QPixmap comparisonImage, bool hasFailed); bool createTestResultsFolderPath(const QString& directory); QString zipAndDeleteTestResultsFolder(); @@ -120,7 +120,8 @@ private: ImageComparer _imageComparer; QString _testResultsFolderPath; - int _index { 1 }; + int _failureIndex{ 1 }; + int _successIndex{ 1 }; // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) const int NUM_DIGITS { 5 }; diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h index 939814df62..5df4e9c921 100644 --- a/tools/auto-tester/src/common.h +++ b/tools/auto-tester/src/common.h @@ -12,9 +12,9 @@ #include -class TestFailure { +class TestResult { public: - TestFailure(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) : + TestResult(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) : _error(error), _pathname(pathname), _expectedImageFilename(expectedImageFilename), diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 4b3762a3b0..99c1ce8e52 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.5"); + setWindowTitle("Auto Tester - v6.6"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index ef6e35ba8a..58189b4795 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -60,20 +60,20 @@ QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultIma return resultPixmap; } -void MismatchWindow::setTestFailure(TestFailure testFailure) { - errorLabel->setText("Similarity: " + QString::number(testFailure._error)); +void MismatchWindow::setTestResult(TestResult testResult) { + errorLabel->setText("Similarity: " + QString::number(testResult._error)); - imagePath->setText("Path to test: " + testFailure._pathname); + imagePath->setText("Path to test: " + testResult._pathname); - expectedFilename->setText(testFailure._expectedImageFilename); - resultFilename->setText(testFailure._actualImageFilename); + expectedFilename->setText(testResult._expectedImageFilename); + resultFilename->setText(testResult._actualImageFilename); - QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); - QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); + QPixmap expectedPixmap = QPixmap(testResult._pathname + testResult._expectedImageFilename); + QPixmap actualPixmap = QPixmap(testResult._pathname + testResult._actualImageFilename); _diffPixmap = computeDiffPixmap( - QImage(testFailure._pathname + testFailure._expectedImageFilename), - QImage(testFailure._pathname + testFailure._actualImageFilename) + QImage(testResult._pathname + testResult._expectedImageFilename), + QImage(testResult._pathname + testResult._actualImageFilename) ); expectedImage->setPixmap(expectedPixmap); diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index f203a2be6a..30c29076b3 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -20,7 +20,7 @@ class MismatchWindow : public QDialog, public Ui::MismatchWindow { public: MismatchWindow(QWidget *parent = Q_NULLPTR); - void setTestFailure(TestFailure testFailure); + void setTestResult(TestResult testResult); UserResponse getUserResponse() { return _userResponse; } From ea8bec30a74fb7d7480f1555722eec3a76aa48dc Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 17:11:25 -0700 Subject: [PATCH 83/90] Update TestRail with results has been updated. --- tools/auto-tester/src/TestRailInterface.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp index 11bc5db4a4..f943935539 100644 --- a/tools/auto-tester/src/TestRailInterface.cpp +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -512,6 +512,8 @@ void TestRailInterface::updateRunWithResults() { // The failed tests are read, formatted and inserted into a set // A failure named 'Failure_1--tests.content.entity.material.apply.avatars.00000' is formatted to 'content/entity/material/apply/avatars' // This is the name of the test in TestRail + // + // A success is named `Success_-tests. ... stream << "from os import listdir\n"; stream << "failed_tests = set()\n"; @@ -519,6 +521,11 @@ void TestRailInterface::updateRunWithResults() { QDir dir(_outputDirectory + "/" + TEMP_NAME); if (dir.exists()) { stream << "for entry in listdir('" + _outputDirectory + "/" + TEMP_NAME + "'):\n"; + + // skip over successes + stream << "\tif entry.split('_')[0] == 'Success':\n"; + stream << "\t\tcontinue\n"; + stream << "\tparts = entry.split('--tests.')[1].split('.')\n"; stream << "\tfailed_test = parts[0]\n"; stream << "\tfor i in range(1, len(parts) - 1):\n"; From 2b59180fef8f3b2accb257b7f976747db0dad060 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Tue, 9 Oct 2018 20:58:59 -0700 Subject: [PATCH 84/90] Writes new format to AWS> --- tools/auto-tester/src/AWSInterface.cpp | 150 +++++++++++++++++++++---- tools/auto-tester/src/AWSInterface.h | 8 +- tools/auto-tester/src/Test.cpp | 7 +- tools/auto-tester/src/TestRunner.cpp | 13 +-- tools/auto-tester/src/TestRunner.h | 2 +- 5 files changed, 141 insertions(+), 39 deletions(-) diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp index a87a8beb41..628db5329c 100644 --- a/tools/auto-tester/src/AWSInterface.cpp +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -131,6 +131,7 @@ void AWSInterface::writeTitle(QTextStream& stream) { const QString months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + stream << "\t" << "\t" << "\n"; stream << "\t" << "\t" << "

Failures for "; stream << months[month.toInt() - 1] << " " << day << ", " << year << ", "; stream << hour << ":" << minute << ":" << second << ", "; @@ -152,8 +153,10 @@ void AWSInterface::writeTable(QTextStream& stream) { // The second stage renames the tests by removing everything up to "--tests." // The third stage renames the directories // The fourth and lasts stage creates the HTML entries - - QStringList originalNames; + // + // Note that failures are processed first, then successes + QStringList originalNamesFailures; + QStringList originalNamesSuccesses; QDirIterator it1(_workingDirectory.toStdString().c_str()); while (it1.hasNext()) { QString nextDirectory = it1.next(); @@ -168,22 +171,41 @@ void AWSInterface::writeTable(QTextStream& stream) { continue; } - originalNames.append(nextDirectory); + // Look at the filename at the end of the path + QStringList parts = nextDirectory.split('/'); + QString name = parts[parts.length() - 1]; + if (name.left(7) == "Failure") { + originalNamesFailures.append(nextDirectory); + } else { + originalNamesSuccesses.append(nextDirectory); + } } - QStringList newNames; - for (int i = 0; i < originalNames.length(); ++i) { - newNames.append(originalNames[i].split("--tests.")[1]); + QStringList newNamesFailures; + for (int i = 0; i < originalNamesFailures.length(); ++i) { + newNamesFailures.append(originalNamesFailures[i].split("--tests.")[1]); + } + + QStringList newNamesSuccesses; + for (int i = 0; i < originalNamesSuccesses.length(); ++i) { + newNamesSuccesses.append(originalNamesSuccesses[i].split("--tests.")[1]); } - _htmlFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURE_FOLDER; - QDir().mkdir(_htmlFolder); + _htmlFailuresFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURES_FOLDER; + QDir().mkdir(_htmlFailuresFolder); - for (int i = 0; i < newNames.length(); ++i) { - QDir().rename(originalNames[i], _htmlFolder + "/" + newNames[i]); + _htmlSuccessesFolder = _workingDirectory + "/" + _resultsFolder + "/" + SUCCESSES_FOLDER; + QDir().mkdir(_htmlSuccessesFolder); + + for (int i = 0; i < newNamesFailures.length(); ++i) { + QDir().rename(originalNamesFailures[i], _htmlFailuresFolder + "/" + newNamesFailures[i]); } - QDirIterator it2((_htmlFolder).toStdString().c_str()); + for (int i = 0; i < newNamesSuccesses.length(); ++i) { + QDir().rename(originalNamesSuccesses[i], _htmlSuccessesFolder + "/" + newNamesSuccesses[i]); + } + + QDirIterator it2((_htmlFailuresFolder).toStdString().c_str()); while (it2.hasNext()) { QString nextDirectory = it2.next(); @@ -211,7 +233,42 @@ void AWSInterface::writeTable(QTextStream& stream) { openTable(stream); } - createEntry(testNumber.toInt(), filename, stream); + createEntry(testNumber.toInt(), filename, stream, true); + } + + closeTable(stream); + stream << "\t" << "\t" << "\n"; + stream << "\t" << "\t" << "

The following tests passed:

"; + + QDirIterator it3((_htmlSuccessesFolder).toStdString().c_str()); + while (it3.hasNext()) { + QString nextDirectory = it3.next(); + + // Skip `.` and `..` directories, as well as the HTML directory + if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + _resultsFolder + "/TestResults--")) { + continue; + } + + QStringList pathComponents = nextDirectory.split('/'); + QString filename = pathComponents[pathComponents.length() - 1]; + int splitIndex = filename.lastIndexOf("."); + QString testName = filename.left(splitIndex).replace(".", " / "); + QString testNumber = filename.right(filename.length() - (splitIndex + 1)); + + // The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table + if (testName != previousTestName) { + if (!previousTestName.isEmpty()) { + closeTable(stream); + } + + previousTestName = testName; + + stream << "\t\t

" << testName << "

\n"; + + openTable(stream); + } + + createEntry(testNumber.toInt(), filename, stream, false); } closeTable(stream); @@ -231,18 +288,35 @@ void AWSInterface::closeTable(QTextStream& stream) { stream << "\t\t\n"; } -void AWSInterface::createEntry(int index, const QString& testResult, QTextStream& stream) { +void AWSInterface::createEntry(int index, const QString& testResult, QTextStream& stream, const bool isFailure) { stream << "\t\t\t\n"; stream << "\t\t\t\t

" << QString::number(index) << "

\n"; // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000` // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000` - QStringList failureNameComponents = testResult.split('/'); - QString failureName = failureNameComponents[failureNameComponents.length() - 1]; + QStringList resultNameComponents = testResult.split('/'); + QString resultName = resultNameComponents[resultNameComponents.length() - 1]; + + QString folder; + bool differenceFileFound; + if (isFailure) { + folder = FAILURES_FOLDER; + differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/Difference Image.png"); + } else { + folder = SUCCESSES_FOLDER; + differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/Difference Image.png"); + } + + + stream << "\t\t\t\t\n"; + stream << "\t\t\t\t\n"; + + if (differenceFileFound) { + stream << "\t\t\t\t\n"; + } else { + stream << "\t\t\t\t

No Image Found

\n"; + } - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; - stream << "\t\t\t\t\n"; stream << "\t\t\t\n"; } @@ -264,7 +338,7 @@ void AWSInterface::updateAWS() { stream << "import boto3\n"; stream << "s3 = boto3.resource('s3')\n\n"; - QDirIterator it1(_htmlFolder.toStdString().c_str()); + QDirIterator it1(_htmlFailuresFolder.toStdString().c_str()); while (it1.hasNext()) { QString nextDirectory = it1.next(); @@ -278,11 +352,41 @@ void AWSInterface::updateAWS() { QStringList parts = nextDirectory.split('/'); QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1]; - const QString imageNames[] { "Actual Image.png", "Expected Image.png", "Difference Image.png" }; + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Actual Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; - for (int i = 0; i < 3; ++i) { - stream << "data = open('" << _workingDirectory << "/" << filename << "/" << imageNames[i] << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << imageNames[i] << "', Body=data)\n\n"; + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Expected Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; + + if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Difference Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + } + } + + QDirIterator it2(_htmlSuccessesFolder.toStdString().c_str()); + while (it2.hasNext()) { + QString nextDirectory = it2.next(); + + // Skip `.` and `..` directories + if (nextDirectory.right(1) == ".") { + continue; + } + + // nextDirectory looks like `D:/t/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/successes/engine.render.effect.bloom.00000` + // We need to concatenate the last 3 components, to get `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/successes/engine.render.effect.bloom.00000` + QStringList parts = nextDirectory.split('/'); + QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1]; + + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Actual Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; + + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Expected Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; + + if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" << "Difference Image.png" << "', 'rb')\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; } } diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h index 08df326000..c5be5f35bb 100644 --- a/tools/auto-tester/src/AWSInterface.h +++ b/tools/auto-tester/src/AWSInterface.h @@ -43,7 +43,7 @@ public: void openTable(QTextStream& stream); void closeTable(QTextStream& stream); - void createEntry(int index, const QString& testResult, QTextStream& stream); + void createEntry(int index, const QString& testResult, QTextStream& stream, const bool isFailure); void updateAWS(); @@ -51,10 +51,12 @@ private: QString _testResults; QString _workingDirectory; QString _resultsFolder; - QString _htmlFolder; + QString _htmlFailuresFolder; + QString _htmlSuccessesFolder; QString _htmlFilename; - const QString FAILURE_FOLDER{ "failures" }; + const QString FAILURES_FOLDER{ "failures" }; + const QString SUCCESSES_FOLDER{ "successes" }; const QString HTML_FILENAME{ "TestResults.html" }; BusyWindow _busyWindow; diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 0e787d1dbf..582f6209af 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -37,7 +38,7 @@ Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) { bool Test::createTestResultsFolderPath(const QString& directory) { QDateTime now = QDateTime::currentDateTime(); - _testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); + _testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT) + "(local)[" + QHostInfo::localHostName() + "]"; QDir testResultsFolder(_testResultsFolderPath); // Create a new test results folder @@ -98,11 +99,11 @@ int Test::compareImageLists() { QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image }; + _mismatchWindow.setTestResult(testResult); + if (similarityIndex < THRESHOLD) { ++numberOfFailures; - _mismatchWindow.setTestResult(testResult); - if (!isInteractiveMode) { appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true); } else { diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 419dcd2d30..4ceb812905 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -9,7 +9,6 @@ // #include "TestRunner.h" -#include #include #include #include @@ -368,7 +367,7 @@ void TestRunner::evaluateResults() { } void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) { - addBuildNumberAndHostnameToResults(zippedFolder); + addBuildNumberToResults(zippedFolder); restoreHighFidelityAppDataFolder(); updateStatusLabel("Testing complete"); @@ -393,16 +392,12 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu _runNow->setEnabled(true); } -void TestRunner::addBuildNumberAndHostnameToResults(QString zippedFolderName) { +void TestRunner::addBuildNumberToResults(QString zippedFolderName) { QString augmentedFilename; if (!_runLatest->isChecked()) { - QStringList filenameParts = zippedFolderName.split("."); - augmentedFilename = - filenameParts[0] + "(" + getPRNumberFromURL(_url->text()) + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + augmentedFilename = zippedFolderName.replace("local", getPRNumberFromURL(_url->text())); } else { - QStringList filenameParts = zippedFolderName.split("."); - augmentedFilename = - filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + augmentedFilename = zippedFolderName.replace("local", _buildInformation.build); } QFile::rename(zippedFolderName, augmentedFilename); } diff --git a/tools/auto-tester/src/TestRunner.h b/tools/auto-tester/src/TestRunner.h index 925ff6077a..e6cb7cd764 100644 --- a/tools/auto-tester/src/TestRunner.h +++ b/tools/auto-tester/src/TestRunner.h @@ -64,7 +64,7 @@ public: void evaluateResults(); void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); - void addBuildNumberAndHostnameToResults(QString zippedFolderName); + void addBuildNumberToResults(QString zippedFolderName); void copyFolder(const QString& source, const QString& destination); From 1d09f6efef04673053cf1fab8a80028d0ffc3753 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 10 Oct 2018 08:12:05 -0700 Subject: [PATCH 85/90] Fixed MacOS error. --- tools/auto-tester/src/TestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 4ceb812905..6e03850c88 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -532,7 +532,7 @@ void TestRunner::parseBuildInformation() { QString platformOfInterest; #ifdef Q_OS_WIN platformOfInterest = "windows"; -#elif Q_OS_MAC +#elif defined(Q_OS_MAC) platformOfInterest = "mac"; #endif QDomElement element = domDocument.documentElement(); From abef242248684035605682944111fcef6ed1b25e Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 17 Oct 2018 13:33:25 -0700 Subject: [PATCH 86/90] Updated user manual. --- tools/auto-tester/Create.PNG | Bin 14207 -> 16996 bytes tools/auto-tester/Evaluate.PNG | Bin 10981 -> 13435 bytes tools/auto-tester/README.md | 55 +++++++++++++----- tools/auto-tester/Run.PNG | Bin 0 -> 20675 bytes .../{TestRail.PNG => Web Interface.PNG} | Bin tools/auto-tester/WebInterface.PNG | Bin 0 -> 17033 bytes tools/auto-tester/Windows.PNG | Bin 11597 -> 14082 bytes 7 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 tools/auto-tester/Run.PNG rename tools/auto-tester/{TestRail.PNG => Web Interface.PNG} (100%) create mode 100644 tools/auto-tester/WebInterface.PNG diff --git a/tools/auto-tester/Create.PNG b/tools/auto-tester/Create.PNG index 4a2a77d2f8f8888e393b3f66013a1e32e0db94c0..85d70e59edadb70f9d0dc4d26e9683fa0e69701a 100644 GIT binary patch literal 16996 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 cae*QJ_NuWx1R@*~tT>wB10swN-8!2M49 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%0DXW|H3yy|DWx@N!#qtyf*w>ILQmq6)pSJ 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. @@ -113,6 +115,34 @@ autoTester.runRecursive(); In this case all recursive scripts, from the selected folder down, are created. Running this function in the tests root folder will create (or update) all the recursive scripts. +# Windows +![](./Windows.PNG) + +This tab is Windows-specific. It provides buttons to hide and show the task bar. + +The task bar should be hidden for all tests that use the primary camera. This is required to ensure that the snapshots are the right size. +# Run +![](./Run.PNG) +The run tab is used to run tests in automatic mode. The tests require the location of a folder to store files in; this folder can safely be re-used for any number of runs (the "Working Folder"). +The test script that is run is `https://github.com/highfidelity/hifi_tests/blob/master/tests/testRecursive.js`. The user can use a different branch' or even repository, if required. +Tests can be run server-less, or with the local host. In the second case, the domain-server and assignment-clients are run before starting Interface. +The default is to run the latest build. The user can select a specific build or PR if desired. +Testing can be started immediately, or run on a schedule. + +A test run is performed in a number of steps: +1. If the latest run has been selected then the `dev-builds.xml` file is downloaded to identify the latest build. +1. The installer is then downloaded. +1. After downloading the High Fidelity application is installed. This requires that UAC be disabled! +1. Any instances of the server-console, assignment-client, domain-server or Interface are killed before running the tests. +1. Interface is run with the appropriate command line parameters. +1. The expected images are then downloaded from GitHub and compared to the actual images from the tests. + +The working folder will ultimately contain the following: +1. A folder named `High Fidelity`. This is where High Fidelity is installed. +1. A folder named `snapshots`. This folder contains the zipped results folders (one for each run). It also contains both the actual images and the expected images from the last run; note that these are deleted before running tests (All PNG files in the folder are deleted. In addition - a text file named `tests_completed.txt` is created at the end of the test script - this signals that Interface did not crash during the test run. +1. The `dev-builds.xml` file, if it was downloaded. +1. The HighFidelity installer. Note that this is always named `HighFidelity-Beta-latest-dev` so as not to store too many installers over time. +1. A log file describing the runs. This file is appended to after each run. # Evaluate ![](./Evaluate.PNG) @@ -140,8 +170,10 @@ Evaluation proceeds in a number of steps: 1. If not in interactive mode, or the user has defined the results as an error, an error is written into the error folder. The error itself is a folder with the 3 images and a small text file containing details. 1. At the end of the test, the folder is zipped and the original folder is deleted. If there are no errors then the zipped folder will be empty. -# TestRail -![](./TestRail.PNG) + +# Web Interface +![](./WebInterface.PNG) +This tab has two functions: updating the TestRail cases, runs and results, and creating web page reports that are stored on AWS. Before updating TestRail, make sure the GitHub user and branch are set correctly. The user should not normally be changed, but the branch may need to be set to the appropriate RC. @@ -209,10 +241,7 @@ A number of Python scripts are created: - `getRuns.py` reads the release names from TestRail - `addRun` is the script that writes to TestRail. -In addition - a file containing all the releases will be created - `runs.txt` -# Windows -![](./Windows.PNG) - -This tab is Windows-specific. It provides buttons to hide and show the task bar. - -The task bar should be hidden for all tests that use the primary camera. This is required to ensure that the snapshots are the right size. \ No newline at end of file +In addition - a file containing all the releases will be created - `runs.txt`. +## Create Web Page +This function requests a zipped results folder and converts it to a web page. The page is created in a user-selecetd working folder. +If the `Update AWS` checkbox is checked then the page will also be copied to AWS, and the appropriate URL will be displayed in the window below the button. diff --git a/tools/auto-tester/Run.PNG b/tools/auto-tester/Run.PNG new file mode 100644 index 0000000000000000000000000000000000000000..29f81299c82eabbf753ead1d5e79fba4eb136282 GIT binary patch literal 20675 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�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 literal 0 HcmV?d00001 diff --git a/tools/auto-tester/TestRail.PNG b/tools/auto-tester/Web Interface.PNG similarity index 100% rename from tools/auto-tester/TestRail.PNG rename to tools/auto-tester/Web Interface.PNG diff --git a/tools/auto-tester/WebInterface.PNG b/tools/auto-tester/WebInterface.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d9d3df3fc642bf97338885bcf9df917be9622656 GIT binary patch literal 17033 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 literal 0 HcmV?d00001 diff --git a/tools/auto-tester/Windows.PNG b/tools/auto-tester/Windows.PNG index 3faf5469bb37288cf7757e7ba00add126b0e636b..32e3bfd53e90e1394317e678718ab1f1ae991d1b 100644 GIT binary patch literal 14082 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 a26Enq2hU1eKp+q(YTM^qssB9u^?w7pP<-70 literal 11597 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
9VLk|&! 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 From 952ffe5d230b9c79c7195a1460d260ad9dd39752 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Wed, 17 Oct 2018 13:36:53 -0700 Subject: [PATCH 87/90] Updated link to Installer. --- tools/auto-tester/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/auto-tester/README.md b/tools/auto-tester/README.md index b5bfefcf67..e029955edc 100644 --- a/tools/auto-tester/README.md +++ b/tools/auto-tester/README.md @@ -14,7 +14,7 @@ Auto-tester has 5 functions, separated into 4 tabs: ## Installation ### Executable -1. Download the installer by browsing to [here](). +1. Download the installer by browsing to [here](). 2. Double click on the installer and install to a convenient location ![](./setup_7z.PNG) 3. To run the auto-tester, double click **auto-tester.exe**. From 4c6185d13965c70953ba49e11ef8b1f3fed97699 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 18 Oct 2018 11:51:04 -0700 Subject: [PATCH 88/90] Set the "--no-updater" flag so that the "Update Available" pop-up won't... --- tools/auto-tester/src/TestRunner.cpp | 2 +- tools/auto-tester/src/ui/AutoTester.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 6e03850c88..8802307151 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -339,7 +339,7 @@ void TestRunner::runInterfaceWithTestScript() { QString testScript = QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; - QString commandLine = exeFile + " --url " + url + " --testScript " + testScript + + QString commandLine = exeFile + " --url " + url + " --no-updater " + " --testScript " + testScript + " quitWhenFinished --testResultsLocation " + _snapshotFolder; interfaceWorker->setCommandLine(commandLine); diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 99c1ce8e52..32457c2224 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -36,7 +36,7 @@ AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Auto Tester - v6.6"); + setWindowTitle("Auto Tester - v6.7"); // Coming soon to an auto-tester near you... //// _helpWindow.textBrowser->setText() From fb0a7274bf3bfeaaa0d8b3589c00abadb5187425 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 18 Oct 2018 16:41:48 -0700 Subject: [PATCH 89/90] Initially disable Installer URL --- tools/auto-tester/src/ui/AutoTester.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index c94d6b3885..b277fbdb2a 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -43,7 +43,7 @@ - 4 + 0 @@ -534,6 +534,9 @@ + + false + 160 From 702e09b4bcd1f3c6250c5e0e5569622841467d40 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 18 Oct 2018 16:43:50 -0700 Subject: [PATCH 90/90] Added no login option. --- tools/auto-tester/src/TestRunner.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 8802307151..674cf6f8e8 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -322,11 +322,12 @@ void TestRunner::startLocalServerProcesses() { system(commandLine.toStdString().c_str()); #endif // Give server processes time to stabilize - QThread::sleep(12); + QThread::sleep(20); } void TestRunner::runInterfaceWithTestScript() { QString exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; + QString snapshotFolder = QString("\"") + QDir::toNativeSeparators(_snapshotFolder) + "\""; QString url = QString("hifi://localhost"); if (_runServerless->isChecked()) { @@ -339,8 +340,8 @@ void TestRunner::runInterfaceWithTestScript() { QString testScript = QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; - QString commandLine = exeFile + " --url " + url + " --no-updater " + " --testScript " + testScript + - " quitWhenFinished --testResultsLocation " + _snapshotFolder; + QString commandLine = exeFile + " --url " + url + " --no-updater --no-login" + " --testScript " + testScript + + " quitWhenFinished --testResultsLocation " + snapshotFolder; interfaceWorker->setCommandLine(commandLine); emit startInterface();