Merge pull request #14015 from NissimHadar/addDailyTests

Add automated daily tests
This commit is contained in:
Sam Gateau 2018-10-19 15:07:00 -07:00 committed by GitHub
commit 1c8ce3da9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 3816 additions and 519 deletions

View file

@ -0,0 +1,285 @@
{
"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/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,
"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 Animation Stats": 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,
"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,
"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,
"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,
"wallet/autoLogout": true
}

View file

@ -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
}
}

View file

@ -0,0 +1,7 @@
{
"metaverse": {
"automatic_networking": "full",
"id": "17b1cb9c-08c4-45aa-9257-163ad3913529"
},
"version": 2.2
}

View file

@ -48,4 +48,19 @@ if (WIN32)
POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
)
# 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" "$<TARGET_FILE_DIR:${TARGET_NAME}>/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 "$<TARGET_FILE_DIR:${TARGET_NAME}>"
)
endif ()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -5,14 +5,16 @@ The auto-tester is a stand alone application that provides a mechanism for regre
* The snapshots are compared to a 'canonical' set of images that have been produced beforehand.
* The result, if any test failed, is a zipped folder describing the failure.
Auto-tester has 4 functions, separated into 4 tabs:
Auto-tester has 5 functions, separated into 4 tabs:
1. Creating tests, MD files and recursive scripts
2. Evaluating the results of running tests
3. TestRail interface
4. Windows task bar utility (Windows only)
1. Windows task bar utility (Windows only)
1. Running tests
1. Evaluating the results of running tests
1. Web interface
## Installation
### Executable
1. Download the installer by browsing to [here](<https://hifi-content.s3.amazonaws.com/nissim/autoTester/AutoTester-Installer.exe>).
1. Download the installer by browsing to [here](<https://hifi-content.s3.amazonaws.com/nissim/autoTester/AutoTester-Installer-v6.6.exe>).
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**.
@ -25,6 +27,17 @@ Python 3 can be downloaded from:
3. Mac <https://www.python.org/downloads/release/python-370/> (**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
#### 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)
@ -36,6 +49,9 @@ This function is used to create/update Expected Images after a successful run of
The user will be asked for the snapshot folder and then the tests root folder. All snapshots located in the snapshot folder will be used to create or update the expected images in the relevant tests.
### Details
As an example - if the snapshots folder contains an image named `tests.content.entity.zone.zoneOrientation.00003.png`, then this file will be copied to `tests/contente/enity/zone/zoneOrientation/ExpectedImage0003.png`.
## Create Tests Outline
### Usage
This function creates an MD file in the (user-selected) tests root folder. The file provides links to both the tests and the MD files.
## Create MD file
### Usage
This function creates a file named `test.md` from a `test.js` script. The user will be asked for the folder containing the test script:
@ -54,13 +70,20 @@ The process to produce the MD file is a simplistic parse of the test script.
### Usage
This function creates all MD files recursively from the user-selected root folder. This can be any folder in the tests hierarchy (e.g. all engine\material tests).
The file provides a hierarchial list of all the tests
## Create Tests Outline
The file provides a hierarchal list of all the tests
## Create testAuto script
### Usage
This function creates an MD file in the (user-selected) tests root folder. The file provides links to both the tests and the MD files.
This function creates a script named `testAuto.js` in a user-selected test folder.
### Details
The script created runs the `test.js` script in the folder in automatic mode. The script is the same for all tests.
## Create all testAuto scripts
### Usage
This function creates all testAuto scripts recursively from the user-selected root folder. This can be any folder in the tests hierarchy (e.g. all engine\material tests).
The file provides a hierarchical list of all the tests
## Create Recursive Script
### Usage
After the user selects a folder within the tests hierarchy, a script is created, named `testRecursive.js`. This script calls all `test.js` scripts in the subfolders.
After the user selects a folder within the tests hierarchy, a script is created, named `testRecursive.js`. This script calls all `test.js` scripts in the sub-folders.
### Details
The various scripts are called in alphabetical order.
@ -92,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)
@ -103,13 +154,13 @@ If any tests have failed, then a zipped folder will be created in the snapshots
### Usage
Before starting the evaluation, 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.
After setting the checkbox as required and pressing Evaluate - the user will be asked for the snapshots folder.
After setting the check-box as required and pressing Evaluate - the user will be asked for the snapshots folder.
### Details
Evaluation proceeds in a number of steps:
1. A folder is created to store any failures
1. The expecetd images are download from GitHub. They are named slightly differently from the snapshots (e.g. `tests.engine.render.effect.highlight.coverage.00000.png` and `tests.engine.render.effect.highlight.coverage.00000_EI.png`).
1. The expected images are download from GitHub. They are named slightly differently from the snapshots (e.g. `tests.engine.render.effect.highlight.coverage.00000.png` and `tests.engine.render.effect.highlight.coverage.00000_EI.png`).
1. The images are then pair-wise compared, using the SSIM algorithm. A fixed threshold is used to define a mismatch.
@ -119,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.
@ -129,9 +182,9 @@ Any access to TestRail will require the TestRail account (default is High Fideli
![](./TestRailSelector.PNG)
- The default test rail user is shown, and can be changed as needed.
- The username is usually the user's email.
- The user-name is usually the user's email.
- The Project ID defaults to 14 - Interface.
- The Suite ID defaults to 1147 - Renderong.
- The Suite ID defaults to 1147 - Rendering.
- The TestRail page provides 3 functions for writing to TestRail.
## Create Test Cases
### Usage
@ -188,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.
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.

BIN
tools/auto-tester/Run.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,413 @@
//
// 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 <QDirIterator>
#include <QMessageBox>
#include <QProcess>
#include <quazip5/quazip.h>
#include <quazip5/JlCompress.h>
AWSInterface::AWSInterface(QObject* parent) : QObject(parent) {
_pythonInterface = new PythonInterface();
_pythonCommand = _pythonInterface->getPythonCommand();
}
void AWSInterface::createWebPageFromResults(const QString& testResults,
const QString& workingDirectory,
QCheckBox* updateAWSCheckBox,
QLineEdit* urlLineEdit) {
_testResults = testResults;
_workingDirectory = workingDirectory;
_urlLineEdit = urlLineEdit;
_urlLineEdit->setEnabled(false);
extractTestFailuresFromZippedFolder();
createHTMLFile();
if (updateAWSCheckBox->isChecked()) {
updateAWS();
}
}
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);
}
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('/');
QString filename = pathComponents[pathComponents.length() - 1];
_resultsFolder = filename.left(filename.length() - 4);
QString resultsPath = _workingDirectory + "/" + _resultsFolder + "/";
QDir().mkdir(resultsPath);
_htmlFilename = resultsPath + HTML_FILENAME;
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(stream);
finishHTMLpage(stream);
file.close();
}
void AWSInterface::startHTMLpage(QTextStream& stream) {
stream << "<!DOCTYPE html>\n";
stream << "<html>\n";
}
void AWSInterface::writeHead(QTextStream& stream) {
stream << "\t" << "<head>\n";
stream << "\t" << "\t" << "<style>\n";
stream << "\t" << "\t" << "\t" << "table, th, td {\n";
stream << "\t" << "\t" << "\t" << "\t" << "border: 1px solid blue;\n";
stream << "\t" << "\t" << "\t" << "}\n";
stream << "\t" << "\t" << "</style>\n";
stream << "\t" << "</head>\n";
}
void AWSInterface::writeBody(QTextStream& stream) {
stream << "\t" << "<body>\n";
writeTitle(stream);
writeTable(stream);
stream << "\t" << "</body>\n";
}
void AWSInterface::finishHTMLpage(QTextStream& stream) {
stream << "</html>\n";
}
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('/');
// 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" << "<font color=\"red\">\n";
stream << "\t" << "\t" << "<h1>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 << "</h1>\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
//
// 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();
// Skip `.` and `..` directories
if (nextDirectory.right(1) == ".") {
continue;
}
// Only process failure folders
if (!nextDirectory.contains("--tests.")) {
continue;
}
// 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 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]);
}
_htmlFailuresFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURES_FOLDER;
QDir().mkdir(_htmlFailuresFolder);
_htmlSuccessesFolder = _workingDirectory + "/" + _resultsFolder + "/" + SUCCESSES_FOLDER;
QDir().mkdir(_htmlSuccessesFolder);
for (int i = 0; i < newNamesFailures.length(); ++i) {
QDir().rename(originalNamesFailures[i], _htmlFailuresFolder + "/" + newNamesFailures[i]);
}
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();
// 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<h2>" << testName << "</h2>\n";
openTable(stream);
}
createEntry(testNumber.toInt(), filename, stream, true);
}
closeTable(stream);
stream << "\t" << "\t" << "<font color=\"blue\">\n";
stream << "\t" << "\t" << "<h1>The following tests passed:</h1>";
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<h2>" << testName << "</h2>\n";
openTable(stream);
}
createEntry(testNumber.toInt(), filename, stream, false);
}
closeTable(stream);
}
void AWSInterface::openTable(QTextStream& stream) {
stream << "\t\t<table>\n";
stream << "\t\t\t<tr>\n";
stream << "\t\t\t\t<th><h1>Test</h1></th>\n";
stream << "\t\t\t\t<th><h1>Actual Image</h1></th>\n";
stream << "\t\t\t\t<th><h1>Expected Image</h1></th>\n";
stream << "\t\t\t\t<th><h1>Difference Image</h1></th>\n";
stream << "\t\t\t</tr>\n";
}
void AWSInterface::closeTable(QTextStream& stream) {
stream << "\t\t</table>\n";
}
void AWSInterface::createEntry(int index, const QString& testResult, QTextStream& stream, const bool isFailure) {
stream << "\t\t\t<tr>\n";
stream << "\t\t\t\t<td><h1>" << QString::number(index) << "</h1></td>\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 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<td><img src=\"./" << folder << "/" << resultName << "/Actual Image.png\" width = \"576\" height = \"324\" ></td>\n";
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n";
if (differenceFileFound) {
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Difference Image.png\" width = \"576\" height = \"324\" ></td>\n";
} else {
stream << "\t\t\t\t<td><h2>No Image Found</h2>\n";
}
stream << "\t\t\t</tr>\n";
}
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(_htmlFailuresFolder.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];
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(_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";
}
}
stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\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(); });
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
QStringList parameters = QStringList() << filename ;
process->start(_pythonCommand, parameters);
}

View file

@ -0,0 +1,72 @@
//
// 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 <QCheckBox>
#include <QLineEdit>
#include <QObject>
#include <QTextStream>
#include "ui/BusyWindow.h"
#include "PythonInterface.h"
class AWSInterface : public QObject {
Q_OBJECT
public:
explicit AWSInterface(QObject* parent = 0);
void createWebPageFromResults(const QString& testResults,
const QString& workingDirectory,
QCheckBox* updateAWSCheckBox,
QLineEdit* urlLineEdit);
void extractTestFailuresFromZippedFolder();
void createHTMLFile();
void startHTMLpage(QTextStream& stream);
void writeHead(QTextStream& stream);
void writeBody(QTextStream& stream);
void finishHTMLpage(QTextStream& stream);
void writeTitle(QTextStream& stream);
void writeTable(QTextStream& stream);
void openTable(QTextStream& stream);
void closeTable(QTextStream& stream);
void createEntry(int index, const QString& testResult, QTextStream& stream, const bool isFailure);
void updateAWS();
private:
QString _testResults;
QString _workingDirectory;
QString _resultsFolder;
QString _htmlFailuresFolder;
QString _htmlSuccessesFolder;
QString _htmlFilename;
const QString FAILURES_FOLDER{ "failures" };
const QString SUCCESSES_FOLDER{ "successes" };
const QString HTML_FILENAME{ "TestResults.html" };
BusyWindow _busyWindow;
PythonInterface* _pythonInterface;
QString _pythonCommand;
QString AWS_BUCKET{ "hifi-qa" };
QLineEdit* _urlLineEdit;
};
#endif // hifi_AWSInterface_h

View file

@ -11,20 +11,20 @@
#include <QtWidgets/QMessageBox>
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;
}

View file

@ -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;

View file

@ -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 <QFile>
#include <QMessageBox>
#include <QProcess>
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;
}

View file

@ -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 <QString>
class PythonInterface {
public:
PythonInterface();
QString getPythonCommand();
private:
const QString _pythonExe{ "python.exe" };
QString _pythonCommand;
};
#endif // hifi_PythonInterface_h

View file

@ -12,6 +12,7 @@
#include <assert.h>
#include <QtCore/QTextStream>
#include <QDirIterator>
#include <QHostInfo>
#include <QImageReader>
#include <QImageWriter>
@ -23,7 +24,10 @@ extern AutoTester* autoTester;
#include <math.h>
Test::Test() {
Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) {
_progressBar = progressBar;
_checkBoxInteractiveMode = checkBoxInteractiveMode;
_mismatchWindow.setModal(true);
if (autoTester) {
@ -34,17 +38,17 @@ Test::Test() {
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
return QDir().mkdir(_testResultsFolderPath);
}
void Test::zipAndDeleteTestResultsFolder() {
QString Test::zipAndDeleteTestResultsFolder() {
QString zippedResultsFileName { _testResultsFolderPath + ".zip" };
QFileInfo fileInfo(zippedResultsFileName);
if (!fileInfo.exists()) {
if (fileInfo.exists()) {
QFile::remove(zippedResultsFileName);
}
@ -55,25 +59,30 @@ void Test::zipAndDeleteTestResultsFolder() {
//In all cases, for the next evaluation
_testResultsFolderPath = "";
_index = 1;
_failureIndex = 1;
_successIndex = 1;
return zippedResultsFileName;
}
bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) {
progressBar->setMinimum(0);
progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
progressBar->setValue(0);
progressBar->setVisible(true);
int 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.
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]);
QImage expectedImage(_expectedImagesFullFilenames[i]);
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
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())) {
@ -83,19 +92,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
similarityIndex = _imageComparer.compareImages(resultImage, expectedImage);
}
if (similarityIndex < THRESHOLD) {
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
};
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
};
_mismatchWindow.setTestFailure(testFailure);
_mismatchWindow.setTestResult(testResult);
if (similarityIndex < THRESHOLD) {
++numberOfFailures;
if (!isInteractiveMode) {
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
success = false;
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true);
} else {
_mismatchWindow.exec();
@ -103,41 +113,53 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
case USER_RESPONSE_PASS:
break;
case USE_RESPONSE_FAIL:
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
success = false;
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true);
break;
case USER_RESPONSE_ABORT:
keepOn = false;
success = false;
break;
default:
assert(false);
break;
}
}
} else {
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), false);
}
progressBar->setValue(i);
_progressBar->setValue(i);
}
progressBar->setVisible(false);
return success;
_progressBar->setVisible(false);
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);
@ -145,10 +167,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();
@ -156,26 +178,34 @@ 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 QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) {
if (testFolder.isNull()) {
// Get list of JPEG images in folder, sorted by name
void Test::startTestsEvaluation(const bool isRunningFromCommandLine,
const bool isRunningInAutomaticTestRun,
const QString& snapshotDirectory,
const QString& branchFromCommandLine,
const QString& userFromCommandLine
) {
_isRunningFromCommandLine = isRunningFromCommandLine;
_isRunningInAutomaticTestRun = isRunningInAutomaticTestRun;
if (snapshotDirectory.isNull()) {
// 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) != "/") {
@ -184,14 +214,14 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
_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;
}
} else {
_snapshotDirectory = testFolder;
_exitWhenComplete = true;
_snapshotDirectory = snapshotDirectory;
_exitWhenComplete = (isRunningFromCommandLine && !isRunningInAutomaticTestRun);
}
// Quit if test results folder could not be created
@ -238,25 +268,28 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
}
}
autoTester->downloadImages(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames);
autoTester->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this);
}
void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) {
bool success = compareImageLists((!isRunningFromCommandline && interactiveMode), progressBar);
void Test::finishTestsEvaluation() {
int numberOfFailures = compareImageLists();
if (interactiveMode && !isRunningFromCommandline) {
if (success) {
if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) {
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");
}
}
zipAndDeleteTestResultsFolder();
QString zippedFolderName = zipAndDeleteTestResultsFolder();
if (_exitWhenComplete) {
exit(0);
}
if (_isRunningInAutomaticTestRun) {
autoTester->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
}
}
bool Test::isAValidDirectory(const QString& pathname) {
@ -302,176 +335,8 @@ void Test::includeTest(QTextStream& textStream, const QString& testPathname) {
textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl;
}
// 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() {
// Select folder to start recursing from
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
_testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
}
createRecursiveScript(_testDirectory, true);
}
// This method creates a `testRecursive.js` script in every sub-folder.
void Test::createAllRecursiveScripts() {
// Select folder to start recursing from
QString previousSelection = _testsRootDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
parent, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (_testsRootDirectory == "") {
_testsRootDirectory = previousSelection;
return;
}
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);
const QString DATE_TIME_FORMAT("MMM d yyyy, h:mm");
textStream << "// This is an automatically generated file, created by auto-tester on " << QDateTime::currentDateTime().toString(DATE_TIME_FORMAT) << endl << 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();
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
// 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('/'));
@ -482,7 +347,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;
@ -497,7 +362,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;
@ -613,9 +478,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() {
bool Test::createFileSetup() {
// Folder selection
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
@ -624,20 +487,18 @@ 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 user canceled then restore previous selection and return
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
return false;
}
createMDFile(_testDirectory);
QMessageBox::information(0, "Success", "MD file has been created");
return true;
}
void Test::createAllMDFiles() {
bool Test::createAllFilesSetup() {
// Select folder to start recursing from
QString previousSelection = _testsRootDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
@ -645,12 +506,32 @@ void Test::createAllMDFiles() {
parent += "/";
}
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent,
QFileDialog::ShowDirsOnly);
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (_testsRootDirectory == "") {
_testsRootDirectory = previousSelection;
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() {
if (!createFileSetup()) {
return;
}
if (createMDFile(_testDirectory)) {
QMessageBox::information(0, "Success", "MD file has been created");
}
}
void Test::createAllMDFiles() {
if (!createAllFilesSetup()) {
return;
}
@ -681,18 +562,18 @@ void Test::createAllMDFiles() {
QMessageBox::information(0, "Success", "MD files have been created");
}
void Test::createMDFile(const QString& _testDirectory) {
bool 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);
return;
return false;
}
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);
@ -705,9 +586,6 @@ void Test::createMDFile(const QString& _testDirectory) {
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);
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";
@ -727,6 +605,223 @@ void Test::createMDFile(const QString& _testDirectory) {
}
mdFile.close();
foreach (auto test, testScriptLines.stepList) {
delete test;
}
testScriptLines.stepList.clear();
return true;
}
void Test::createTestAutoScript() {
if (!createFileSetup()) {
return;
}
if (createTestAutoScript(_testDirectory)) {
QMessageBox::information(0, "Success", "'autoTester.js` script has been created");
}
}
void Test::createAllTestAutoScripts() {
if (!createAllFilesSetup()) {
return;
}
// 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");
}
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 false;
}
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();
return true;
}
// 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() {
if (!createFileSetup()) {
return;
}
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() {
if (!createAllFilesSetup()) {
return;
}
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() {
@ -739,7 +834,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;
@ -949,3 +1044,19 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) {
void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) {
_testRailCreateMode = testRailCreateMode;
}
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()) {
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, updateAWSCheckBox, urlLineEdit);
}

View file

@ -16,6 +16,7 @@
#include <QtCore/QRegularExpression>
#include <QProgressBar>
#include "AWSInterface.h"
#include "ImageComparer.h"
#include "ui/MismatchWindow.h"
#include "TestRailInterface.h"
@ -41,28 +42,41 @@ 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 bool isRunningInAutomaticTestRun,
const QString& snapshotDirectory = QString(),
const QString& branchFromCommandLine = QString(),
const QString& userFromCommandLine = QString());
void createRecursiveScript();
void createAllRecursiveScripts();
void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode);
void finishTestsEvaluation();
void createTests();
void createTestsOutline();
bool createFileSetup();
bool createAllFilesSetup();
void createMDFile();
void createAllMDFiles();
void createMDFile(const QString& topLevelDirectory);
bool createMDFile(const QString& directory);
void createTestAutoScript();
void createAllTestAutoScripts();
bool createTestAutoScript(const QString& directory);
void createTestRailTestCases();
void createTestRailRun();
void updateTestRailRunResult();
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
void createRecursiveScript();
void createAllRecursiveScripts();
void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode);
int compareImageLists();
QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory);
@ -70,10 +84,10 @@ 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);
void zipAndDeleteTestResultsFolder();
QString zipAndDeleteTestResultsFolder();
static bool isAValidDirectory(const QString& pathname);
QString extractPathFromTestsDown(const QString& fullPath);
@ -84,12 +98,20 @@ public:
void setTestRailCreateMode(TestRailCreateMode testRailCreateMode);
void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit);
private:
QProgressBar* _progressBar;
QCheckBox* _checkBoxInteractiveMode;
bool _isRunningFromCommandLine{ false };
bool _isRunningInAutomaticTestRun{ false };
const QString TEST_FILENAME { "test.js" };
const QString TEST_RESULTS_FOLDER { "TestResults" };
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
const double THRESHOLD{ 0.96 };
const double THRESHOLD{ 0.935 };
QDir _imageDirectory;
@ -98,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 };
@ -132,8 +155,9 @@ private:
bool _exitWhenComplete{ false };
TestRailInterface _testRailInterface;
TestRailCreateMode _testRailCreateMode { PYTHON };
AWSInterface _awsInterface;
};
#endif // hifi_test_h

View file

@ -16,66 +16,40 @@
#include <QDateTime>
#include <QFile>
#include <QHostInfo>
#include <QMessageBox>
#include <QTextStream>
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_PROJECT_ID);
////_testRailTestCasesSelectorWindow.setProjectID(2);
_testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);
_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_PROJECT_ID);
////_testRailRunSelectorWindow.setProjectID(2);
_testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);
_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_PROJECT_ID);
////_testRailResultsSelectorWindow.setProjectID(2);
_testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID);
_testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);
////_testRailResultsSelectorWindow.setSuiteID(2);
_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() {
@ -240,7 +214,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());
@ -258,7 +232,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());
@ -276,7 +250,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());
@ -377,8 +351,9 @@ 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, SIGNAL(finished(int)), process, SLOT(deleteLater()));
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
@ -489,8 +464,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";
@ -503,7 +478,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<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
@ -511,6 +486,7 @@ void TestRailInterface::addRun() {
process->start(_pythonCommand, parameters);
}
}
void TestRailInterface::updateRunWithResults() {
QString filename = _outputDirectory + "/updateRunWithResults.py";
if (QFile::exists(filename)) {
@ -536,17 +512,27 @@ 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_<n>-tests. ...
stream << "from os import listdir\n";
stream << "failed_tests = set()\n";
stream << "for entry in listdir('" + _outputDirectory + "/" + tempName + "'):\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";
// 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";
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";
@ -580,7 +566,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();
@ -590,7 +582,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<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
@ -765,6 +757,7 @@ void TestRailInterface::getReleasesFromTestRail() {
QProcess* process = new QProcess();
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&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);
@ -779,10 +772,6 @@ void TestRailInterface::createTestSuitePython(const QString& testDirectory,
_userGitHub = userGitHub;
_branchGitHub = branchGitHub;
if (!setPythonCommand()) {
return;
}
if (!requestTestRailTestCasesDataFromUser()) {
return;
}
@ -812,6 +801,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
@ -908,7 +898,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");
@ -1036,7 +1026,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"
@ -1087,6 +1076,7 @@ void TestRailInterface::getTestSectionsFromTestRail() {
QProcess* process = new QProcess();
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&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);
@ -1125,6 +1115,7 @@ void TestRailInterface::getRunsFromTestRail() {
QProcess* process = new QProcess();
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { updateRunsComboData(exitCode, exitStatus); });
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
QStringList parameters = QStringList() << filename;
@ -1134,10 +1125,6 @@ void TestRailInterface::getRunsFromTestRail() {
void TestRailInterface::createTestRailRun(const QString& outputDirectory) {
_outputDirectory = outputDirectory;
if (!setPythonCommand()) {
return;
}
if (!requestTestRailRunDataFromUser()) {
return;
}
@ -1151,10 +1138,7 @@ void TestRailInterface::createTestRailRun(const QString& outputDirectory) {
void TestRailInterface::updateTestRailRunResults(const QString& testResults, const QString& tempDirectory) {
_outputDirectory = tempDirectory;
if (!setPythonCommand()) {
return;
}
_testResults = testResults;
if (!requestTestRailResultsDataFromUser()) {
return;
@ -1164,11 +1148,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);
}

View file

@ -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 <QProcess>
#include <QString>
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,14 +87,14 @@ public:
void addRun();
void updateRunWithResults();
bool setPythonCommand();
void extractTestFailuresFromZippedFolder(const QString& testResults, const QString& tempDirectory);
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;
@ -111,13 +110,11 @@ private:
QString _suiteID;
QString _testDirectory;
QString _testResults;
QString _outputDirectory;
QString _userGitHub;
QString _branchGitHub;
const QString pythonExe{ "python.exe" };
QString _pythonCommand;
QStringList _releaseNames;
QStringList _sectionNames;
@ -126,7 +123,10 @@ private:
QStringList _runNames;
std::vector<int> _runIDs;
QString tempName{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" };
QString TEMP_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" };
PythonInterface* _pythonInterface;
QString _pythonCommand;
};
#endif

View file

@ -0,0 +1,608 @@
//
// TestRunner.cpp
//
// Created by Nissim Hadar on 1 Sept 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 <QThread>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog>
#include "ui/AutoTester.h"
extern AutoTester* autoTester;
#ifdef Q_OS_WIN
#include <windows.h>
#include <tlhelp32.h>
#endif
TestRunner::TestRunner(std::vector<QCheckBox*> dayCheckboxes,
std::vector<QCheckBox*> timeEditCheckboxes,
std::vector<QTimeEdit*> timeEdits,
QLabel* workingFolderLabel,
QCheckBox* runServerless,
QCheckBox* runLatest,
QLineEdit* url,
QPushButton* runNow,
QObject* parent) :
QObject(parent) {
_dayCheckboxes = dayCheckboxes;
_timeEditCheckboxes = timeEditCheckboxes;
_timeEdits = timeEdits;
_workingFolderLabel = workingFolderLabel;
_runServerless = runServerless;
_runLatest = runLatest;
_url = url;
_runNow = runNow;
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() {
// 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";
_logFile.setFileName(_workingFolder + "/log.txt");
autoTester->enableRunTabControls();
_workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder));
_timer = new QTimer(this);
connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime()));
_timer->start(30 * 1000); //time specified in ms
}
void TestRunner::run() {
_runNow->setEnabled(false);
_testStartDateTime = QDateTime::currentDateTime();
_automatedTestIsRunning = true;
// Initial setup
_branch = autoTester->getSelectedBranch();
_user = autoTester->getSelectedUser();
// This will be restored at the end of the tests
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;
urls << DEV_BUILD_XML_URL;
filenames << DEV_BUILD_XML_FILENAME;
updateStatusLabel("Downloading Build XML");
buildXMLDownloaded = false;
autoTester->downloadFiles(urls, _workingFolder, filenames, (void*)this);
// `downloadComplete` will run after download has completed
}
void TestRunner::downloadComplete() {
if (!buildXMLDownloaded) {
// Download of Build XML has completed
buildXMLDownloaded = true;
// Download the High Fidelity installer
QStringList urls;
QStringList filenames;
if (_runLatest->isChecked()) {
parseBuildInformation();
_installerFilename = INSTALLER_FILENAME_LATEST;
urls << _buildInformation.url;
filenames << _installerFilename;
} else {
QString urlText = _url->text();
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() {
// 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 + "/" + _installerFilename;
QString commandLine =
"\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder);
installerWorker->setCommandLine(commandLine);
emit startInstaller();
}
void TestRunner::installationComplete() {
verifyInstallationSucceeded();
createSnapshotFolder();
updateStatusLabel("Running tests");
if (!_runServerless->isChecked()) {
startLocalServerProcesses();
}
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 interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe");
QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe");
QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe");
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);
}
#endif
}
void TestRunner::saveExistingHighFidelityAppDataFolder() {
QString dataDirectory{ "NOT FOUND" };
#ifdef Q_OS_WIN
dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming";
#endif
if (_runLatest->isChecked()) {
_appDataFolder = dataDirectory + "\\High Fidelity";
} else {
// We are running a PR build
_appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->text());
}
_savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME;
if (_savedAppDataFolder.exists()) {
_savedAppDataFolder.removeRecursively();
}
if (_appDataFolder.exists()) {
// The original folder is saved in a unique name
_appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path());
}
// Copy an "empty" AppData folder (i.e. no entities)
copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path());
}
void TestRunner::createSnapshotFolder() {
_snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME;
// 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" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") {
QFile::remove(filename);
}
}
} else {
QDir().mkdir(_snapshotFolder);
}
}
void TestRunner::killProcesses() {
#ifdef Q_OS_WIN
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
}
void TestRunner::startLocalServerProcesses() {
#ifdef Q_OS_WIN
QString commandLine;
commandLine = "start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\"";
system(commandLine.toStdString().c_str());
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
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()) {
// Move to an empty area
url = "file:///~serverless/tutorial.json";
} else {
url = "hifi://localhost";
}
QString testScript =
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js";
QString commandLine = exeFile + " --url " + url + " --no-updater --no-login" + " --testScript " + testScript +
" quitWhenFinished --testResultsLocation " + snapshotFolder;
interfaceWorker->setCommandLine(commandLine);
emit startInterface();
}
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
}
void TestRunner::evaluateResults() {
updateStatusLabel("Evaluating results");
autoTester->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user);
}
void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) {
addBuildNumberToResults(zippedFolder);
restoreHighFidelityAppDataFolder();
updateStatusLabel("Testing complete");
QDateTime currentDateTime = QDateTime::currentDateTime();
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;
_runNow->setEnabled(true);
}
void TestRunner::addBuildNumberToResults(QString zippedFolderName) {
QString augmentedFilename;
if (!_runLatest->isChecked()) {
augmentedFilename = zippedFolderName.replace("local", getPRNumberFromURL(_url->text()));
} else {
augmentedFilename = zippedFolderName.replace("local", _buildInformation.build);
}
QFile::rename(zippedFolderName, augmentedFilename);
}
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 {
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);
}
}
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 };
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;
break;
}
}
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);
}
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("/");
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() <= 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[filenameParts.size() - 2];
} 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 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";
#elif defined(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;
}
int Worker::runCommand() {
int result = system(_commandLine.toStdString().c_str());
emit commandComplete();
return result;
}

View file

@ -0,0 +1,151 @@
//
// TestRunner.h
//
// Created by Nissim Hadar on 1 Sept 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 <QCheckBox>
#include <QDir>
#include <QLabel>
#include <QLineEdit>
#include <QObject>
#include <QPushButton>
#include <QThread>
#include <QTimeEdit>
#include <QTimer>
class BuildInformation {
public:
QString build;
QString url;
};
class Worker;
class TestRunner : public QObject {
Q_OBJECT
public:
explicit TestRunner(std::vector<QCheckBox*> dayCheckboxes,
std::vector<QCheckBox*> timeEditCheckboxes,
std::vector<QTimeEdit*> timeEdits,
QLabel* workingFolderLabel,
QCheckBox* runServerless,
QCheckBox* runLatest,
QLineEdit* url,
QPushButton* runNow,
QObject* parent = 0);
~TestRunner();
void setWorkingFolder();
void run();
void downloadComplete();
void runInstaller();
void verifyInstallationSucceeded();
void saveExistingHighFidelityAppDataFolder();
void restoreHighFidelityAppDataFolder();
void createSnapshotFolder();
void killProcesses();
void startLocalServerProcesses();
void runInterfaceWithTestScript();
void evaluateResults();
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
void addBuildNumberToResults(QString zippedFolderName);
void copyFolder(const QString& source, const QString& destination);
void updateStatusLabel(const QString& message);
void appendLog(const QString& message);
QString getInstallerNameFromURL(const QString& url);
QString getPRNumberFromURL(const QString& url);
void parseBuildInformation();
private slots:
void checkTime();
void installationComplete();
void interfaceExecutionComplete();
signals:
void startInstaller();
void startInterface();
private:
bool _automatedTestIsRunning{ false };
const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" };
QString _installerURL;
QString _installerFilename;
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;
QString _workingFolder;
QString _installationFolder;
QString _snapshotFolder;
const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" };
const QString SNAPSHOT_FOLDER_NAME{ "snapshots" };
QString _branch;
QString _user;
std::vector<QCheckBox*> _dayCheckboxes;
std::vector<QCheckBox*> _timeEditCheckboxes;
std::vector<QTimeEdit*> _timeEdits;
QLabel* _workingFolderLabel;
QCheckBox* _runServerless;
QCheckBox* _runLatest;
QLineEdit* _url;
QPushButton* _runNow;
QTimer* _timer;
QFile _logFile;
QDateTime _testStartDateTime;
QThread* installerThread;
QThread* interfaceThread;
Worker* installerWorker;
Worker* interfaceWorker;
BuildInformation _buildInformation;
};
class Worker : public QObject {
Q_OBJECT
public:
void setCommandLine(const QString& commandLine);
public slots:
int runCommand();
signals:
void commandComplete();
void startInstaller();
void startInterface();
private:
QString _commandLine;
};
#endif // hifi_testRunner_h

View file

@ -12,9 +12,9 @@
#include <QtCore/QString>
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),

View file

@ -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();
}

View file

@ -28,34 +28,86 @@ 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()
_ui.statusLabel->setText("");
_ui.plainTextEdit->setReadOnly(true);
setWindowTitle("Auto Tester - v6.7");
// Coming soon to an auto-tester near you...
//// _helpWindow.textBrowser->setText()
}
AutoTester::~AutoTester() {
delete _signalMapper;
if (_test) {
delete _test;
}
if (_testRunner) {
delete _testRunner;
}
}
void AutoTester::setup() {
_test = new Test();
if (_test) {
delete _test;
}
_test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode);
std::vector<QCheckBox*> 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<QCheckBox*> timeEditCheckboxes;
timeEditCheckboxes.emplace_back(_ui.timeEdit1checkBox);
timeEditCheckboxes.emplace_back(_ui.timeEdit2checkBox);
timeEditCheckboxes.emplace_back(_ui.timeEdit3checkBox);
timeEditCheckboxes.emplace_back(_ui.timeEdit4checkBox);
std::vector<QTimeEdit*> timeEdits;
timeEdits.emplace_back(_ui.timeEdit1);
timeEdits.emplace_back(_ui.timeEdit2);
timeEdits.emplace_back(_ui.timeEdit3);
timeEdits.emplace_back(_ui.timeEdit4);
if (_testRunner) {
delete _testRunner;
}
_testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlLineEdit, _ui.runNowButton);
}
void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) {
_isRunningFromCommandline = true;
_test->startTestsEvaluation(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) {
if (index == 1 || index == 2) {
_ui.userTextEdit->setDisabled(false);
_ui.branchTextEdit->setDisabled(false);
if (index == 0 || index == 2 || index == 3) {
_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);
}
}
void AutoTester::on_evaluateTestsButton_clicked() {
_test->startTestsEvaluation();
_test->startTestsEvaluation(false, false);
}
void AutoTester::on_createRecursiveScriptButton_clicked() {
@ -78,6 +130,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();
}
@ -90,6 +150,28 @@ 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();
}
void AutoTester::on_checkBoxRunLatest_clicked() {
_ui.urlLineEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked());
}
void AutoTester::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) {
_testRunner->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
}
void AutoTester::on_updateTestRailRunResultsButton_clicked() {
_test->updateTestRailRunResult();
}
@ -130,7 +212,11 @@ void AutoTester::on_createXMLScriptRadioButton_clicked() {
_test->setTestRailCreateMode(XML);
}
void AutoTester::downloadImage(const QUrl& url) {
void AutoTester::on_createWebPagePushButton_clicked() {
_test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit);
}
void AutoTester::downloadFile(const QUrl& url) {
_downloaders.emplace_back(new Downloader(url, this));
connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map()));
@ -139,70 +225,86 @@ 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, void *caller) {
connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int)));
_directoryName = directoryName;
_filenames = filenames;
_caller = caller;
_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);
foreach (auto downloader, _downloaders) {
delete downloader;
}
connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveImage(int)));
_downloaders.clear();
for (int i = 0; i < _numberOfFilesToDownload; ++i) {
downloadFile(URLs[i]);
}
}
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)));
_test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar);
if (_numberOfFilesDownloaded == _numberOfFilesToDownload) {
disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int)));
if (_caller == _test) {
_test->finishTestsEvaluation();
} else if (_caller == _testRunner) {
_testRunner->downloadComplete();
}
} else {
_ui.progressBar->setValue(_numberOfImagesDownloaded);
_ui.progressBar->setValue(_numberOfFilesDownloaded);
}
}
void AutoTester::about() {
QMessageBox::information(0, "About", QString("Built ") + __DATE__ + " : " + __TIME__);
QMessageBox::information(0, "About", QString("Built ") + __DATE__ + ", " + __TIME__);
}
void AutoTester::content() {
helpWindow.show();
_helpWindow.show();
}
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) {
_ui.statusLabel->setText(status);
}
void AutoTester::appendLogWindow(const QString& message) {
_ui.plainTextEdit->appendPlainText(message);
}

View file

@ -19,19 +19,28 @@
#include "../Test.h"
#include "HelpWindow.h"
#include "../TestRunner.h"
#include "../AWSInterface.h"
class AutoTester : public QMainWindow {
Q_OBJECT
public:
AutoTester(QWidget *parent = Q_NULLPTR);
AutoTester(QWidget* parent = Q_NULLPTR);
~AutoTester();
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 downloadImage(const QUrl& url);
void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames);
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
void downloadFile(const QUrl& url);
void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller);
void setUserText(const QString& user);
QString getSelectedUser();
@ -39,21 +48,35 @@ public:
void setBranchText(const QString& branch);
QString getSelectedBranch();
void enableRunTabControls();
void updateStatusLabel(const QString& status);
void appendLogWindow(const QString& message);
private slots:
void on_tabWidget_currentChanged(int index);
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();
void on_createTestAutoScriptButton_clicked();
void on_createAllTestAutoScriptsButton_clicked();
void on_createTestsOutlineButton_clicked();
void on_createTestRailTestCasesButton_clicked();
void on_createTestRailRunButton_clicked();
void on_setWorkingFolderButton_clicked();
void on_runNowButton_clicked();
void on_checkBoxRunLatest_clicked();
void on_updateTestRailRunResultsButton_clicked();
void on_hideTaskbarButton_clicked();
@ -62,16 +85,21 @@ private slots:
void on_createPythonScriptRadioButton_clicked();
void on_createXMLScriptRadioButton_clicked();
void on_createWebPagePushButton_clicked();
void on_closeButton_clicked();
void saveImage(int index);
void saveFile(int index);
void about();
void content();
private:
Ui::AutoTesterClass _ui;
Test* _test;
Test* _test{ nullptr };
TestRunner* _testRunner{ nullptr };
AWSInterface _awsInterface;
std::vector<Downloader*> _downloaders;
@ -82,13 +110,15 @@ private:
// Used to enable passing a parameter to slots
QSignalMapper* _signalMapper;
int _numberOfImagesToDownload { 0 };
int _numberOfImagesDownloaded { 0 };
int _index { 0 };
int _numberOfFilesToDownload{ 0 };
int _numberOfFilesDownloaded{ 0 };
int _index{ 0 };
bool _isRunningFromCommandline { false };
bool _isRunningFromCommandline{ false };
HelpWindow helpWindow;
HelpWindow _helpWindow;
void* _caller;
};
#endif // hifi_AutoTester_h
#endif // hifi_AutoTester_h

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>432</width>
<height>734</height>
<width>720</width>
<height>870</height>
</rect>
</property>
<property name="sizePolicy">
@ -23,8 +23,8 @@
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>166</x>
<y>610</y>
<x>470</x>
<y>750</y>
<width>100</width>
<height>40</height>
</rect>
@ -36,10 +36,10 @@
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>12</x>
<x>45</x>
<y>140</y>
<width>408</width>
<height>461</height>
<width>630</width>
<height>580</height>
</rect>
</property>
<property name="currentIndex">
@ -52,8 +52,8 @@
<widget class="QPushButton" name="createTestsButton">
<property name="geometry">
<rect>
<x>96</x>
<y>20</y>
<x>195</x>
<y>60</y>
<width>220</width>
<height>40</height>
</rect>
@ -65,8 +65,8 @@
<widget class="QPushButton" name="createMDFileButton">
<property name="geometry">
<rect>
<x>96</x>
<y>100</y>
<x>70</x>
<y>180</y>
<width>220</width>
<height>40</height>
</rect>
@ -78,8 +78,8 @@
<widget class="QPushButton" name="createAllMDFilesButton">
<property name="geometry">
<rect>
<x>96</x>
<y>150</y>
<x>320</x>
<y>180</y>
<width>220</width>
<height>40</height>
</rect>
@ -91,8 +91,8 @@
<widget class="QPushButton" name="createTestsOutlineButton">
<property name="geometry">
<rect>
<x>96</x>
<y>230</y>
<x>195</x>
<y>120</y>
<width>220</width>
<height>40</height>
</rect>
@ -104,8 +104,8 @@
<widget class="QPushButton" name="createRecursiveScriptButton">
<property name="geometry">
<rect>
<x>96</x>
<y>310</y>
<x>70</x>
<y>300</y>
<width>220</width>
<height>40</height>
</rect>
@ -117,8 +117,8 @@
<widget class="QPushButton" name="createAllRecursiveScriptsButton">
<property name="geometry">
<rect>
<x>96</x>
<y>360</y>
<x>320</x>
<y>300</y>
<width>220</width>
<height>40</height>
</rect>
@ -127,30 +127,436 @@
<string>Create all Recursive Scripts</string>
</property>
</widget>
<widget class="QPushButton" name="createTestAutoScriptButton">
<property name="geometry">
<rect>
<x>70</x>
<y>240</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create testAuto script</string>
</property>
</widget>
<widget class="QPushButton" name="createAllTestAutoScriptsButton">
<property name="geometry">
<rect>
<x>320</x>
<y>240</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create all testAuto scripts</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Windows</string>
</attribute>
<widget class="QPushButton" name="hideTaskbarButton">
<property name="geometry">
<rect>
<x>200</x>
<y>130</y>
<width>211</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Hide Windows Taskbar</string>
</property>
</widget>
<widget class="QPushButton" name="showTaskbarButton">
<property name="geometry">
<rect>
<x>200</x>
<y>200</y>
<width>211</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Show Windows Taskbar</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Run</string>
</attribute>
<widget class="QPushButton" name="runNowButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>10</x>
<y>160</y>
<width>161</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Run now</string>
</property>
</widget>
<widget class="QGroupBox" name="daysGroupBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>20</x>
<y>240</y>
<width>91</width>
<height>241</height>
</rect>
</property>
<property name="title">
<string>Days</string>
</property>
<widget class="QCheckBox" name="sundayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>210</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Sunday</string>
</property>
</widget>
<widget class="QCheckBox" name="wednesdayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>90</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Wednesday</string>
</property>
</widget>
<widget class="QCheckBox" name="tuesdayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Tuesday</string>
</property>
</widget>
<widget class="QCheckBox" name="thursdayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Thursday</string>
</property>
</widget>
<widget class="QCheckBox" name="fridayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>150</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Friday</string>
</property>
</widget>
<widget class="QCheckBox" name="saturdayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>180</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Saturday</string>
</property>
</widget>
<widget class="QCheckBox" name="mondayCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>80</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Monday</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="timesGroupBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>130</x>
<y>240</y>
<width>161</width>
<height>191</height>
</rect>
</property>
<property name="title">
<string>Times</string>
</property>
<widget class="QTimeEdit" name="timeEdit1">
<property name="geometry">
<rect>
<x>30</x>
<y>20</y>
<width>118</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QTimeEdit" name="timeEdit2">
<property name="geometry">
<rect>
<x>30</x>
<y>60</y>
<width>118</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QTimeEdit" name="timeEdit3">
<property name="geometry">
<rect>
<x>30</x>
<y>100</y>
<width>118</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QTimeEdit" name="timeEdit4">
<property name="geometry">
<rect>
<x>30</x>
<y>140</y>
<width>118</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QCheckBox" name="timeEdit1checkBox">
<property name="geometry">
<rect>
<x>10</x>
<y>23</y>
<width>21</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QCheckBox" name="timeEdit2checkBox">
<property name="geometry">
<rect>
<x>10</x>
<y>63</y>
<width>21</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QCheckBox" name="timeEdit3checkBox">
<property name="geometry">
<rect>
<x>10</x>
<y>103</y>
<width>21</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="QCheckBox" name="timeEdit4checkBox">
<property name="geometry">
<rect>
<x>10</x>
<y>143</y>
<width>21</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<widget class="QPushButton" name="setWorkingFolderButton">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>161</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Set Working Folder</string>
</property>
</widget>
<widget class="QLabel" name="workingFolderLabel">
<property name="geometry">
<rect>
<x>190</x>
<y>20</y>
<width>321</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>(not set...)</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="geometry">
<rect>
<x>300</x>
<y>210</y>
<width>311</width>
<height>331</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="workingFolderLabel_2">
<property name="geometry">
<rect>
<x>300</x>
<y>170</y>
<width>41</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Status:</string>
</property>
</widget>
<widget class="QLabel" name="statusLabel">
<property name="geometry">
<rect>
<x>350</x>
<y>170</y>
<width>271</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>#######</string>
</property>
</widget>
<widget class="QCheckBox" name="checkBoxServerless">
<property name="geometry">
<rect>
<x>20</x>
<y>70</y>
<width>120</width>
<height>20</height>
</rect>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If unchecked, will not show results during evaluation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Server-less</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
<widget class="QCheckBox" name="checkBoxRunLatest">
<property name="geometry">
<rect>
<x>20</x>
<y>100</y>
<width>120</width>
<height>20</height>
</rect>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If unchecked, will not show results during evaluation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Run Latest</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="workingFolderLabel_3">
<property name="geometry">
<rect>
<x>128</x>
<y>95</y>
<width>21</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>URL</string>
</property>
</widget>
<widget class="QLineEdit" name="urlLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>160</x>
<y>100</y>
<width>451</width>
<height>21</height>
</rect>
</property>
</widget>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Evaluate</string>
</attribute>
<widget class="QProgressBar" name="progressBar">
<property name="geometry">
<rect>
<x>90</x>
<y>100</y>
<width>255</width>
<height>23</height>
</rect>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
<widget class="QCheckBox" name="checkBoxInteractiveMode">
<property name="geometry">
<rect>
<x>90</x>
<y>50</y>
<width>131</width>
<x>200</x>
<y>180</y>
<width>120</width>
<height>20</height>
</rect>
</property>
@ -164,8 +570,8 @@
<widget class="QPushButton" name="evaluateTestsButton">
<property name="geometry">
<rect>
<x>200</x>
<y>40</y>
<x>330</x>
<y>170</y>
<width>101</width>
<height>40</height>
</rect>
@ -177,14 +583,14 @@
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>TestRail</string>
<string>Web Interface</string>
</attribute>
<widget class="QPushButton" name="updateTestRailRunResultsButton">
<property name="geometry">
<rect>
<x>180</x>
<y>160</y>
<width>161</width>
<x>240</x>
<y>220</y>
<width>160</width>
<height>40</height>
</rect>
</property>
@ -195,8 +601,8 @@
<widget class="QRadioButton" name="createPythonScriptRadioButton">
<property name="geometry">
<rect>
<x>80</x>
<y>40</y>
<x>170</x>
<y>100</y>
<width>95</width>
<height>20</height>
</rect>
@ -211,9 +617,9 @@
<widget class="QPushButton" name="createTestRailRunButton">
<property name="geometry">
<rect>
<x>180</x>
<y>100</y>
<width>161</width>
<x>240</x>
<y>160</y>
<width>160</width>
<height>40</height>
</rect>
</property>
@ -224,9 +630,9 @@
<widget class="QPushButton" name="createTestRailTestCasesButton">
<property name="geometry">
<rect>
<x>180</x>
<y>40</y>
<width>161</width>
<x>240</x>
<y>100</y>
<width>160</width>
<height>40</height>
</rect>
</property>
@ -237,8 +643,8 @@
<widget class="QRadioButton" name="createXMLScriptRadioButton">
<property name="geometry">
<rect>
<x>80</x>
<y>60</y>
<x>170</x>
<y>120</y>
<width>95</width>
<height>20</height>
</rect>
@ -247,45 +653,86 @@
<string>XML</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Windows</string>
</attribute>
<widget class="QPushButton" name="hideTaskbarButton">
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>100</x>
<y>100</y>
<width>211</width>
<height>40</height>
<x>10</x>
<y>30</y>
<width>601</width>
<height>300</height>
</rect>
</property>
<property name="text">
<string>Hide Windows Taskbar</string>
<property name="title">
<string>TestRail</string>
</property>
</widget>
<widget class="QPushButton" name="showTaskbarButton">
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>100</x>
<y>170</y>
<width>211</width>
<height>40</height>
<x>10</x>
<y>350</y>
<width>601</width>
<height>151</height>
</rect>
</property>
<property name="text">
<string>Show Windows Taskbar</string>
<property name="title">
<string>Amazon Web Services</string>
</property>
<widget class="QPushButton" name="createWebPagePushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>240</x>
<y>30</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create Web Page</string>
</property>
</widget>
<widget class="QCheckBox" name="updateAWSCheckBox">
<property name="geometry">
<rect>
<x>150</x>
<y>42</y>
<width>81</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Update AWS</string>
</property>
</widget>
<widget class="QLineEdit" name="awsURLLineEdit">
<property name="geometry">
<rect>
<x>20</x>
<y>90</y>
<width>561</width>
<height>21</height>
</rect>
</property>
</widget>
</widget>
<zorder>groupBox</zorder>
<zorder>updateTestRailRunResultsButton</zorder>
<zorder>createPythonScriptRadioButton</zorder>
<zorder>createTestRailRunButton</zorder>
<zorder>createTestRailTestCasesButton</zorder>
<zorder>createXMLScriptRadioButton</zorder>
<zorder>groupBox_2</zorder>
</widget>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>110</x>
<y>90</y>
<width>81</width>
<x>120</x>
<y>80</y>
<width>110</width>
<height>16</height>
</rect>
</property>
@ -298,32 +745,12 @@
<string>GitHub Branch</string>
</property>
</widget>
<widget class="QTextEdit" name="branchTextEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>85</y>
<width>140</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QTextEdit" name="userTextEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>47</y>
<width>140</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>110</x>
<y>50</y>
<width>81</width>
<x>120</x>
<y>40</y>
<width>110</width>
<height>16</height>
</rect>
</property>
@ -336,13 +763,46 @@
<string>GitHub User</string>
</property>
</widget>
<widget class="QProgressBar" name="progressBar">
<property name="geometry">
<rect>
<x>80</x>
<y>760</y>
<width>255</width>
<height>23</height>
</rect>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
<widget class="QLineEdit" name="userLineEdit">
<property name="geometry">
<rect>
<x>220</x>
<y>40</y>
<width>161</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="branchLineEdit">
<property name="geometry">
<rect>
<x>220</x>
<y>80</y>
<width>161</width>
<height>21</height>
</rect>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>432</width>
<width>720</width>
<height>21</height>
</rect>
</property>
@ -388,6 +848,53 @@
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>userLineEdit</tabstop>
<tabstop>branchLineEdit</tabstop>
<tabstop>createTestsButton</tabstop>
<tabstop>createMDFileButton</tabstop>
<tabstop>createAllMDFilesButton</tabstop>
<tabstop>createTestsOutlineButton</tabstop>
<tabstop>createRecursiveScriptButton</tabstop>
<tabstop>createAllRecursiveScriptsButton</tabstop>
<tabstop>createTestAutoScriptButton</tabstop>
<tabstop>createAllTestAutoScriptsButton</tabstop>
<tabstop>hideTaskbarButton</tabstop>
<tabstop>showTaskbarButton</tabstop>
<tabstop>runNowButton</tabstop>
<tabstop>sundayCheckBox</tabstop>
<tabstop>wednesdayCheckBox</tabstop>
<tabstop>tuesdayCheckBox</tabstop>
<tabstop>thursdayCheckBox</tabstop>
<tabstop>fridayCheckBox</tabstop>
<tabstop>saturdayCheckBox</tabstop>
<tabstop>mondayCheckBox</tabstop>
<tabstop>timeEdit1</tabstop>
<tabstop>timeEdit2</tabstop>
<tabstop>timeEdit3</tabstop>
<tabstop>timeEdit4</tabstop>
<tabstop>timeEdit1checkBox</tabstop>
<tabstop>timeEdit2checkBox</tabstop>
<tabstop>timeEdit3checkBox</tabstop>
<tabstop>timeEdit4checkBox</tabstop>
<tabstop>setWorkingFolderButton</tabstop>
<tabstop>plainTextEdit</tabstop>
<tabstop>checkBoxServerless</tabstop>
<tabstop>checkBoxRunLatest</tabstop>
<tabstop>urlLineEdit</tabstop>
<tabstop>checkBoxInteractiveMode</tabstop>
<tabstop>evaluateTestsButton</tabstop>
<tabstop>updateTestRailRunResultsButton</tabstop>
<tabstop>createPythonScriptRadioButton</tabstop>
<tabstop>createTestRailRunButton</tabstop>
<tabstop>createTestRailTestCasesButton</tabstop>
<tabstop>createXMLScriptRadioButton</tabstop>
<tabstop>createWebPagePushButton</tabstop>
<tabstop>updateAWSCheckBox</tabstop>
<tabstop>awsURLLineEdit</tabstop>
<tabstop>closeButton</tabstop>
<tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -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];
@ -55,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);

View file

@ -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; }

View file

@ -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);

View file

@ -19,12 +19,12 @@ TestRailRunSelectorWindow::TestRailRunSelectorWindow(QWidget *parent) {
projectIDLineEdit->setValidator(new QIntValidator(1, 999, this));
}
void TestRailRunSelectorWindow::reset() {
urlLineEdit->setDisabled(false);
userLineEdit->setDisabled(false);
passwordLineEdit->setDisabled(false);
projectIDLineEdit->setDisabled(false);
suiteIDLineEdit->setDisabled(false);
OKButton->setDisabled(true);
sectionsComboBox->setDisabled(true);
@ -35,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);

View file

@ -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);