Merge pull request #14015 from NissimHadar/addDailyTests
Add automated daily tests
285
tools/auto-tester/AppDataHighFidelity/Interface.json
Normal 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
|
||||
}
|
BIN
tools/auto-tester/AppDataHighFidelity/Interface/AccountInfo.bin
Normal 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"metaverse": {
|
||||
"automatic_networking": "full",
|
||||
"id": "17b1cb9c-08c4-45aa-9257-163ad3913529"
|
||||
},
|
||||
"version": 2.2
|
||||
}
|
|
@ -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 ()
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 13 KiB |
|
@ -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
|
||||

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

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

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

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

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

|
||||
|
||||
# Web Interface
|
||||

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

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

|
||||
|
||||
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
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 11 KiB |
BIN
tools/auto-tester/Web Interface.PNG
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
tools/auto-tester/WebInterface.PNG
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 14 KiB |
413
tools/auto-tester/src/AWSInterface.cpp
Normal 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);
|
||||
}
|
72
tools/auto-tester/src/AWSInterface.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
32
tools/auto-tester/src/PythonInterface.cpp
Normal 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;
|
||||
}
|
26
tools/auto-tester/src/PythonInterface.h
Normal 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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
608
tools/auto-tester/src/TestRunner.cpp
Normal 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;
|
||||
}
|
151
tools/auto-tester/src/TestRunner.h
Normal 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
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></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><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|