AssetProcessorManagerTest.cpp 286 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "AssetProcessorManagerTest.h"
  9. #include "native/AssetManager/PathDependencyManager.h"
  10. #include "native/AssetManager/assetScannerWorker.h"
  11. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  12. #include <AzToolsFramework/Asset/AssetProcessorMessages.h>
  13. #include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
  14. #include <AzToolsFramework/ToolsFileUtils/ToolsFileUtils.h>
  15. #include <AzTest/AzTest.h>
  16. #include <limits>
  17. #include <AzCore/Jobs/JobContext.h>
  18. #include <AzCore/Jobs/JobManager.h>
  19. #include <AzCore/Jobs/JobManagerComponent.h>
  20. #include <AzCore/Jobs/JobManagerDesc.h>
  21. #include <AzCore/Utils/Utils.h>
  22. #include <tests/assetmanager/AssetManagerTestingBase.h>
  23. #include <utilities/ProductOutputUtil.h>
  24. using namespace AssetProcessor;
  25. AssetProcessorManager_Test::AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent /*= 0*/)
  26. :AssetProcessorManager(config, parent)
  27. {
  28. }
  29. AssetProcessorManager_Test::~AssetProcessorManager_Test()
  30. {
  31. }
  32. bool AssetProcessorManager_Test::CheckJobKeyToJobRunKeyMap(AZStd::string jobKey)
  33. {
  34. return (m_jobKeyToJobRunKeyMap.find(jobKey) != m_jobKeyToJobRunKeyMap.end());
  35. }
  36. AssetProcessorManagerTest::AssetProcessorManagerTest()
  37. : m_argc(0)
  38. , m_argv(0)
  39. {
  40. m_qApp.reset(new QCoreApplication(m_argc, m_argv));
  41. qRegisterMetaType<AssetProcessor::JobEntry>("JobEntry");
  42. qRegisterMetaType<AssetBuilderSDK::ProcessJobResponse>("ProcessJobResponse");
  43. qRegisterMetaType<AZStd::string>("AZStd::string");
  44. qRegisterMetaType<AssetProcessor::AssetScanningStatus>("AssetProcessor::AssetScanningStatus");
  45. qRegisterMetaType<QSet<AssetFileInfo>>("QSet<AssetFileInfo>");
  46. }
  47. bool AssetProcessorManagerTest::BlockUntilIdle(int millisecondsMax)
  48. {
  49. QElapsedTimer limit;
  50. limit.start();
  51. if(AZ::Debug::Trace::Instance().IsDebuggerPresent())
  52. {
  53. millisecondsMax = std::numeric_limits<int>::max();
  54. }
  55. // Always run at least once so that if we're in an idle state to start, we don't end up skipping the loop before finishing all the queued work
  56. do
  57. {
  58. QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
  59. } while ((!m_isIdling) && (limit.elapsed() < millisecondsMax));
  60. // and then once more, so that any queued events as a result of the above finish.
  61. QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
  62. return m_isIdling;
  63. }
  64. void AssetProcessorManagerTest::SetUp()
  65. {
  66. using namespace testing;
  67. using ::testing::NiceMock;
  68. using namespace AssetProcessor;
  69. using namespace AzToolsFramework::AssetDatabase;
  70. AssetProcessorTest::SetUp();
  71. qRegisterMetaType<AssetProcessor::SourceAssetReference>("SourceAssetReference");
  72. m_assetRootDir = QDir(m_databaseLocationListener.GetAssetRootDir().c_str());
  73. m_scopeDir = AZStd::make_unique<UnitTestUtils::ScopedDir>();
  74. m_scopeDir->Setup(m_assetRootDir.path());
  75. m_data = AZStd::make_unique<StaticData>();
  76. m_data->m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
  77. m_data->m_descriptor = AZ::JobManagerComponent::CreateDescriptor();
  78. m_data->m_descriptor->Reflect(m_data->m_serializeContext.get());
  79. m_data->m_jobManagerEntity = aznew AZ::Entity{};
  80. m_data->m_jobManagerEntity->CreateComponent<AZ::JobManagerComponent>();
  81. m_data->m_jobManagerEntity->Init();
  82. m_data->m_jobManagerEntity->Activate();
  83. m_config.reset(new AssetProcessor::PlatformConfiguration());
  84. m_mockApplicationManager.reset(new AssetProcessor::MockApplicationManager());
  85. AssetUtilities::ResetAssetRoot();
  86. auto registry = AZ::SettingsRegistry::Get();
  87. auto cacheRootKey =
  88. AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_cache_path";
  89. registry->Set(cacheRootKey, m_assetRootDir.absoluteFilePath("Cache").toUtf8().constData());
  90. auto projectPathKey =
  91. AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
  92. AZ::IO::FixedMaxPath enginePath;
  93. registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  94. registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
  95. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
  96. m_gameName = AssetUtilities::ComputeProjectName("AutomatedTesting", true);
  97. AssetUtilities::ResetAssetRoot();
  98. QDir newRoot;
  99. AssetUtilities::ComputeEngineRoot(newRoot, &m_assetRootDir);
  100. QDir cacheRoot;
  101. AssetUtilities::ComputeProjectCacheRoot(cacheRoot);
  102. QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(cacheRoot.absolutePath());
  103. m_normalizedCacheRootDir.setPath(normalizedCacheRoot);
  104. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
  105. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/a.txt"));
  106. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/b.txt"));
  107. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/c.txt"));
  108. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/d.txt"));
  109. m_config->EnablePlatform({ "pc", { "host", "renderer", "desktop" } }, true);
  110. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder1"), "subfolder1", "subfolder1", false, true, m_config->GetEnabledPlatforms(), 1));
  111. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder2"), "subfolder2", "subfolder2", false, true, m_config->GetEnabledPlatforms()));
  112. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder3"), "subfolder3", "subfolder3", false, true, m_config->GetEnabledPlatforms(), 1));
  113. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder4"), "subfolder4", "subfolder4", false, true, m_config->GetEnabledPlatforms(), 1));
  114. m_config->AddMetaDataType("assetinfo", "");
  115. m_config->AddIntermediateScanFolder();
  116. m_aUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/a.txt"))).GetValueOr(AZ::Uuid());
  117. m_bUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/b.txt"))).GetValueOr(AZ::Uuid());
  118. m_cUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/c.txt"))).GetValueOr(AZ::Uuid());
  119. m_dUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/d.txt"))).GetValueOr(AZ::Uuid());
  120. ASSERT_FALSE(m_aUuid.IsNull());
  121. ASSERT_FALSE(m_bUuid.IsNull());
  122. ASSERT_FALSE(m_cUuid.IsNull());
  123. ASSERT_FALSE(m_dUuid.IsNull());
  124. AssetRecognizer rec;
  125. rec.m_name = "txt files";
  126. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  127. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  128. rec.m_supportsCreateJobs = false;
  129. rec.m_supportsCreateJobs = false;
  130. ASSERT_TRUE(m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec));
  131. m_mockApplicationManager->BusConnect();
  132. m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
  133. m_assetProcessorManager->SetMetaCreationDelay(0);
  134. m_errorAbsorber->Clear();
  135. m_isIdling = false;
  136. m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState)
  137. {
  138. m_isIdling = newState;
  139. });
  140. PopulateDatabase();
  141. }
  142. void AssetProcessorManagerTest::TearDown()
  143. {
  144. m_data->m_jobManagerEntity->Deactivate();
  145. delete m_data->m_jobManagerEntity;
  146. delete m_data->m_descriptor;
  147. m_data = nullptr;
  148. QObject::disconnect(m_idleConnection);
  149. m_mockApplicationManager->BusDisconnect();
  150. m_mockApplicationManager->UnRegisterAllBuilders();
  151. AssetUtilities::ResetAssetRoot();
  152. AssetUtilities::ResetGameName();
  153. m_assetProcessorManager.reset();
  154. m_mockApplicationManager.reset();
  155. m_config.reset();
  156. m_qApp.reset();
  157. m_scopeDir.reset();
  158. AssetProcessor::AssetProcessorTest::TearDown();
  159. }
  160. void AssetProcessorManagerTest::CreateSourceAndFile(const char* tempFolderRelativePath)
  161. {
  162. auto absolutePath = m_assetRootDir.absoluteFilePath(tempFolderRelativePath);
  163. auto scanFolder = m_config->GetScanFolderForFile(absolutePath);
  164. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(absolutePath));
  165. QString relPath;
  166. m_config->ConvertToRelativePath(absolutePath, scanFolder, relPath);
  167. auto uuid = AssetUtilities::GetSourceUuid(SourceAssetReference(absolutePath.toUtf8().constData()));
  168. ASSERT_TRUE(uuid);
  169. AzToolsFramework::AssetDatabase::SourceDatabaseEntry source(scanFolder->ScanFolderID(), relPath.toUtf8().constData(), uuid.GetValue(), "fingerprint");
  170. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSource(source));
  171. }
  172. void AssetProcessorManagerTest::PopulateDatabase()
  173. {
  174. using namespace AzToolsFramework::AssetDatabase;
  175. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder(
  176. m_assetRootDir.absoluteFilePath("subfolder1").toUtf8().constData(), "temp path", "temp path");
  177. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetScanFolder(scanFolder));
  178. CreateSourceAndFile("subfolder1/a.txt");
  179. CreateSourceAndFile("subfolder1/b.txt");
  180. CreateSourceAndFile("subfolder1/c.txt");
  181. CreateSourceAndFile("subfolder1/d.txt");
  182. }
  183. TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDSuccess)
  184. {
  185. // Here we first mark a job for an asset complete and than fetch jobs info using the job log api to verify
  186. // Next we mark another job for that same asset as queued, and we again fetch jobs info from the api to verify,
  187. using namespace AzToolsFramework::AssetSystem;
  188. using namespace AssetProcessor;
  189. QString relFileName("assetProcessorManagerTest.txt");
  190. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
  191. QString watchFolder = m_assetRootDir.absoluteFilePath("subfolder1");
  192. JobEntry entry;
  193. entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder, relFileName);
  194. entry.m_jobKey = "txt";
  195. entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
  196. entry.m_jobRunKey = 1;
  197. UnitTestUtils::CreateDummyFile(m_normalizedCacheRootDir.absoluteFilePath("pc/outputfile.txt"));
  198. AssetBuilderSDK::ProcessJobResponse jobResponse;
  199. jobResponse.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  200. jobResponse.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("outputfile.txt"));
  201. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, entry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, jobResponse));
  202. // let events bubble through:
  203. QCoreApplication::processEvents(QEventLoop::AllEvents);
  204. QCoreApplication::processEvents(QEventLoop::AllEvents);
  205. AZ::Uuid uuid = AssetUtilities::GetSourceUuid(entry.m_sourceAssetReference).GetValue();
  206. AssetJobsInfoRequest request;
  207. request.m_assetId = AZ::Data::AssetId(uuid, 0);
  208. request.m_escalateJobs = false;
  209. AssetJobsInfoResponse response;
  210. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(request, response);
  211. EXPECT_TRUE(response.m_isSuccess);
  212. EXPECT_EQ(1, response.m_jobList.size());
  213. ASSERT_GT(response.m_jobList.size(), 0); // Assert on this to exit early if needed, otherwise indexing m_jobList later will crash.
  214. EXPECT_EQ(JobStatus::Completed, response.m_jobList[0].m_status);
  215. EXPECT_STRCASEEQ(relFileName.toUtf8().data(), response.m_jobList[0].m_sourceFile.c_str());
  216. m_assetProcessorManager->OnJobStatusChanged(entry, JobStatus::Queued);
  217. response.m_isSuccess = false;
  218. response.m_jobList.clear();
  219. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(request, response);
  220. EXPECT_TRUE(response.m_isSuccess);
  221. EXPECT_EQ(1, response.m_jobList.size());
  222. ASSERT_GT(response.m_jobList.size(), 0); // Assert on this to exit early if needed, otherwise indexing m_jobList later will crash.
  223. EXPECT_EQ(JobStatus::Queued, response.m_jobList[0].m_status);
  224. EXPECT_STRCASEEQ(relFileName.toUtf8().data(), response.m_jobList[0].m_sourceFile.c_str());
  225. EXPECT_STRCASEEQ(m_assetRootDir.filePath("subfolder1").toUtf8().data(), response.m_jobList[0].m_watchFolder.c_str());
  226. ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0);
  227. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
  228. ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
  229. }
  230. using AssetProcessorManagerUuid = UnitTests::AssetManagerTestingBase;
  231. TEST_F(AssetProcessorManagerUuid, UuidUpdated_SendsAssetRemovedMessage)
  232. {
  233. // This test simulates a source control update where someone else has moved a file and created a new file with the same name as the old one (and a new UUID)
  234. // This will appear to AP as a content change + UUID change
  235. using namespace AssetBuilderSDK;
  236. CreateBuilder("builder", "*.in", "stage2", false, ProductOutputFlags::ProductAsset);
  237. AZ::Interface<IUuidRequests>::Get()->EnableGenerationForTypes({ ".in" });
  238. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  239. AZStd::string testFilename = "test.in";
  240. AZ::IO::Path filePath = (scanFolderDir / testFilename).AsPosix();
  241. AssetProcessor::SourceAssetReference sourceAsset{ filePath.c_str() };
  242. UnitTestUtils::CreateDummyFileAZ(filePath, "unit test file");
  243. ProcessFileMultiStage(1, true, sourceAsset);
  244. auto metadataInterface = AZ::Interface<AzToolsFramework::IMetadataRequests>::Get();
  245. // Get the existing UUID entry
  246. AzToolsFramework::MetaUuidEntry uuidEntry;
  247. ASSERT_TRUE(metadataInterface->GetValue(filePath, AzToolsFramework::UuidUtilComponent::UuidKey, uuidEntry));
  248. ASSERT_FALSE(uuidEntry.m_uuid.IsNull());
  249. auto oldUuid = uuidEntry.m_uuid;
  250. // Make a new UUID
  251. uuidEntry.m_uuid = AZ::Uuid::CreateRandom();
  252. // Save it out
  253. ASSERT_TRUE(metadataInterface->SetValue(filePath, AzToolsFramework::UuidUtilComponent::UuidKey, uuidEntry));
  254. AZ::Interface<IUuidRequests>::Get()->FileChanged(AzToolsFramework::MetadataManager::ToMetadataPath(filePath));
  255. using namespace AzFramework::AssetSystem;
  256. AZStd::vector<AssetNotificationMessage> notifications;
  257. auto connection = QObject::connect(
  258. m_assetProcessorManager.get(),
  259. &AssetProcessorManager::AssetMessage,
  260. [&notifications](AssetNotificationMessage message)
  261. {
  262. notifications.push_back(message);
  263. });
  264. // Run the file again
  265. ProcessFileMultiStage(1, true, sourceAsset);
  266. // Verify asset removed and asset changed messages were sent
  267. std::sort(
  268. notifications.begin(),
  269. notifications.end(),
  270. [](auto lhs, auto rhs)
  271. {
  272. return lhs.m_type > rhs.m_type;
  273. });
  274. ASSERT_EQ(notifications.size(), 2);
  275. EXPECT_EQ(notifications[0].m_data, "test.stage2");
  276. EXPECT_EQ(notifications[0].m_assetId, AZ::Data::AssetId(oldUuid, AssetSubId));
  277. EXPECT_EQ(notifications[0].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  278. EXPECT_EQ(notifications[1].m_data, "test.stage2");
  279. EXPECT_EQ(notifications[1].m_assetId, AZ::Data::AssetId(uuidEntry.m_uuid, AssetSubId));
  280. EXPECT_EQ(notifications[1].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  281. }
  282. class MetadataOverrides : public UnitTests::AssetManagerTestingBase
  283. {
  284. public:
  285. void SetUp() override
  286. {
  287. UnitTests::AssetManagerTestingBase::SetUp();
  288. using namespace AssetBuilderSDK;
  289. // Set up a custom builder with a ProcessJob stage that will output 2 files, one of which is intentionally
  290. // designed to output a product with a name that conflicts with the prefixing scheme (the (2) prefix).
  291. // .stage1 is the input and .stage2 is the output. This unit test framework currently requires those extensions.
  292. m_builderInfoHandler.CreateBuilderDesc(
  293. "stage1",
  294. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  295. { AssetBuilderPattern{ "*.stage1", AssetBuilderPattern::Wildcard } },
  296. CreateJobStage("stage1", false),
  297. [](const ProcessJobRequest& request, ProcessJobResponse& response)
  298. {
  299. AZ::IO::FixedMaxPath outputFile = AZ::IO::FixedMaxPath(request.m_sourceFile);
  300. outputFile.ReplaceExtension("stage2");
  301. outputFile = outputFile.Filename();
  302. AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Copy(
  303. request.m_fullPath.c_str(), (AZ::IO::FixedMaxPath(request.m_tempDirPath) / outputFile).c_str());
  304. EXPECT_TRUE(result);
  305. auto product = JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName("stage2"), AssetSubId };
  306. product.m_outputFlags = ProductOutputFlags::ProductAsset;
  307. product.m_dependenciesHandled = true;
  308. // Output an extra product which is already prefixed
  309. // This tests an edge case where removing the prefixes during finalization in the wrong order can result in overwriting the main product
  310. auto extraFilePath =
  311. AZ::IO::Path(request.m_tempDirPath) / "(2)file.stage2";
  312. AZ::Utils::WriteFile("unit test file", extraFilePath.Native());
  313. auto extraProduct = JobProduct{ extraFilePath.c_str(), AZ::Data::AssetType::CreateName("extra"), ExtraAssetSubId };
  314. extraProduct.m_outputFlags = ProductOutputFlags::ProductAsset;
  315. extraProduct.m_dependenciesHandled = true;
  316. response.m_outputProducts.push_back(extraProduct);
  317. response.m_outputProducts.push_back(product);
  318. response.m_resultCode = ProcessJobResult_Success;
  319. },
  320. "fingerprint");
  321. // Enable metadata for our file type
  322. AZ::Interface<IUuidRequests>::Get()->EnableGenerationForTypes({ ".stage1" });
  323. AZ::IO::Path assetRootDir = m_databaseLocationListener.GetAssetRootDir();
  324. m_sourceA = SourceAssetReference{ assetRootDir / "folder" / "subfolder" / "file.stage1" };
  325. m_sourceB = SourceAssetReference{ assetRootDir / "folder2" / "subfolder" / "file.stage1" };
  326. }
  327. void SetupScanfolders(AZ::IO::Path assetRootDir, const AZStd::vector<AssetBuilderSDK::PlatformInfo>& platforms) override
  328. {
  329. UnitTests::AssetManagerTestingBase::SetupScanfolders(assetRootDir, platforms);
  330. m_platformConfig->AddScanFolder(
  331. AssetProcessor::ScanFolderInfo{ (assetRootDir / "folder2").c_str(), "folder2", "folder2", false, true, platforms });
  332. }
  333. void VerifyProducts()
  334. {
  335. AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
  336. EXPECT_TRUE(this->m_stateData->GetProductsByProductName("pc/subfolder/file.stage2", products));
  337. EXPECT_EQ(products.size(), 1);
  338. products = {};
  339. auto productName =
  340. AZStd::string::format("pc/subfolder/%sfile.stage2", ProductOutputUtil::GetFinalPrefix(m_sourceB.ScanFolderId()).c_str());
  341. EXPECT_TRUE(m_stateData->GetProductsByProductName(productName.c_str(), products));
  342. EXPECT_EQ(products.size(), 1);
  343. auto io = AZ::IO::FileIOBase::GetInstance();
  344. // SourceA
  345. // SourceA is highest priority so its files should exist without the prefix
  346. EXPECT_TRUE(io->Exists(MakePath("subfolder/file.stage2", false).c_str()));
  347. EXPECT_TRUE(io->Exists(MakePath("subfolder/(2)file.stage2", false).c_str()));
  348. auto prefix = ProductOutputUtil::GetFinalPrefix(m_sourceA.ScanFolderId());
  349. EXPECT_FALSE(io->Exists(MakePath(AZStd::string::format("subfolder/%s(2)file.stage2", prefix.c_str()).c_str(), false).c_str()));
  350. // SourceB
  351. // SourceB is lower priority so its files should exist with the prefix
  352. prefix = ProductOutputUtil::GetFinalPrefix(m_sourceB.ScanFolderId());
  353. EXPECT_TRUE(io->Exists(MakePath(AZStd::string::format("subfolder/%sfile.stage2", prefix.c_str()).c_str(), false).c_str()));
  354. EXPECT_TRUE(io->Exists(MakePath(AZStd::string::format("subfolder/%s(2)file.stage2", prefix.c_str()).c_str(), false).c_str()));
  355. auto fileContentsResult = AZ::Utils::ReadFile(MakePath("subfolder/file.stage2", false).c_str());
  356. ASSERT_TRUE(fileContentsResult);
  357. EXPECT_STREQ(fileContentsResult.GetValue().c_str(), "unit test file A");
  358. }
  359. AssetProcessor::SourceAssetReference m_sourceA;
  360. AssetProcessor::SourceAssetReference m_sourceB;
  361. };
  362. TEST_F(MetadataOverrides, MetadataOverrides_HighestPriorityProcessedFirst_OutputsCorrectly)
  363. {
  364. // Create 2 source files with the same relative name, one in each scanfolder
  365. AZ::Utils::WriteFile("unit test file A", m_sourceA.AbsolutePath().c_str());
  366. AZ::Utils::WriteFile("unit test file B", m_sourceB.AbsolutePath().c_str());
  367. // Process both files
  368. ProcessFileMultiStage(1, true, m_sourceA);
  369. ProcessFileMultiStage(1, true, m_sourceB);
  370. VerifyProducts();
  371. }
  372. TEST_F(MetadataOverrides, MetadataOverrides_LowestPriorityProcessedFirst_OutputsCorrectly)
  373. {
  374. // Create 2 source files with the same relative name, one in each scanfolder
  375. AZ::Utils::WriteFile("unit test file A", m_sourceA.AbsolutePath().c_str());
  376. AZ::Utils::WriteFile("unit test file B", m_sourceB.AbsolutePath().c_str());
  377. // Process both files
  378. ProcessFileMultiStage(1, false, m_sourceB);
  379. auto io = AZ::IO::FileIOBase::GetInstance();
  380. auto prefix = ProductOutputUtil::GetFinalPrefix(m_sourceB.ScanFolderId());
  381. EXPECT_FALSE(io->Exists(MakePath("subfolder/file.stage2", false).c_str()));
  382. EXPECT_FALSE(io->Exists(MakePath("subfolder/(2)file.stage2", false).c_str()));
  383. EXPECT_TRUE(io->Exists(MakePath(AZStd::string::format("subfolder/%sfile.stage2", prefix.c_str()).c_str(), false).c_str()));
  384. EXPECT_TRUE(io->Exists(MakePath(AZStd::string::format("subfolder/%s(2)file.stage2", prefix.c_str()).c_str(), false).c_str()));
  385. ProcessFileMultiStage(1, false, m_sourceA);
  386. VerifyProducts();
  387. }
  388. TEST_F(MetadataOverrides, MetadataOverrides_LowestPriorityCreatedFirst_OutputsCorrectly)
  389. {
  390. // Create and process the low priority file first
  391. AZ::Utils::WriteFile("unit test file B", m_sourceB.AbsolutePath().c_str());
  392. ProcessFileMultiStage(1, false, m_sourceB);
  393. auto io = AZ::IO::FileIOBase::GetInstance();
  394. EXPECT_TRUE(io->Exists(MakePath("subfolder/file.stage2", false).c_str()));
  395. EXPECT_TRUE(io->Exists(MakePath("subfolder/(2)file.stage2", false).c_str()));
  396. // Create and process the high priority file second
  397. AZ::Utils::WriteFile("unit test file A", m_sourceA.AbsolutePath().c_str());
  398. ProcessFileMultiStage(1, false, m_sourceA);
  399. VerifyProducts();
  400. }
  401. using AssetProcessorManagerFinishTests = UnitTests::AssetManagerTestingBase;
  402. TEST_F(AssetProcessorManagerFinishTests, IntermediateAsset_AnalysisCountHitsZero)
  403. {
  404. // Test that FinishedAnalysis occurs and that we can reliably determine both APM and RC have finished working when intermediate assets
  405. // are involved
  406. using namespace AssetBuilderSDK;
  407. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  408. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  409. int remainingFiles = 0;
  410. int maxWaitingFiles = 0;
  411. bool finishedAnalysisOccurred = false;
  412. bool finishedAnalysisAndIdle = false;
  413. bool idle = false;
  414. QObject::connect(
  415. m_assetProcessorManager.get(),
  416. &AssetProcessor::AssetProcessorManager::FinishedAnalysis,
  417. [&remainingFiles, &maxWaitingFiles, &finishedAnalysisOccurred, &idle, &finishedAnalysisAndIdle, this](int count)
  418. {
  419. finishedAnalysisOccurred = true;
  420. if (count > maxWaitingFiles)
  421. {
  422. maxWaitingFiles = count;
  423. }
  424. remainingFiles = count;
  425. if (idle && remainingFiles == 0 && finishedAnalysisOccurred && m_rc->IsIdle())
  426. {
  427. EXPECT_FALSE(finishedAnalysisAndIdle);
  428. finishedAnalysisAndIdle = true;
  429. }
  430. });
  431. QObject::connect(
  432. m_assetProcessorManager.get(),
  433. &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState,
  434. [&idle, &remainingFiles, &finishedAnalysisAndIdle, &finishedAnalysisOccurred, this](bool state)
  435. {
  436. idle = state;
  437. if (idle && remainingFiles == 0 && finishedAnalysisOccurred && m_rc->IsIdle())
  438. {
  439. EXPECT_FALSE(finishedAnalysisAndIdle);
  440. finishedAnalysisAndIdle = true;
  441. }
  442. });
  443. ProcessFileMultiStage(2, true);
  444. QCoreApplication::processEvents(); // Execute FinishAnalysis
  445. EXPECT_TRUE(finishedAnalysisOccurred);
  446. EXPECT_TRUE(finishedAnalysisAndIdle);
  447. }
  448. TEST_F(AssetProcessorManagerFinishTests, MultipleFiles_WithDuplicateJobs_AnalysisCountHitsZero)
  449. {
  450. // Test that FinishedAnalysis emits a non-zero value when multiple files are queued up and that having the same file submitted twice
  451. // does not result in the counter being stuck at a non-zero value.
  452. using namespace AssetBuilderSDK;
  453. CreateBuilder("stage1", "*.stage1", "stage2", false, ProductOutputFlags::ProductAsset);
  454. // Connect RC to APM
  455. QObject::connect(
  456. m_rc.get(),
  457. &AssetProcessor::RCController::FileCompiled,
  458. m_assetProcessorManager.get(),
  459. &AssetProcessor::AssetProcessorManager::AssetProcessed,
  460. Qt::UniqueConnection);
  461. QObject::connect(
  462. m_rc.get(),
  463. &AssetProcessor::RCController::FileFailed,
  464. m_assetProcessorManager.get(),
  465. &AssetProcessor::AssetProcessorManager::AssetFailed);
  466. QObject::connect(
  467. m_rc.get(),
  468. &AssetProcessor::RCController::FileCancelled,
  469. m_assetProcessorManager.get(),
  470. &AssetProcessor::AssetProcessorManager::AssetCancelled);
  471. int remainingFiles = 0;
  472. int maxWaitingFiles = 0;
  473. bool finishedAnalysisOccurred = false;
  474. QObject::connect(
  475. m_assetProcessorManager.get(),
  476. &AssetProcessor::AssetProcessorManager::FinishedAnalysis,
  477. [&remainingFiles, &maxWaitingFiles, &finishedAnalysisOccurred](int count)
  478. {
  479. finishedAnalysisOccurred = true;
  480. if (count > maxWaitingFiles)
  481. {
  482. maxWaitingFiles = count;
  483. }
  484. remainingFiles = count;
  485. });
  486. // Set up a second file to process
  487. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  488. AZStd::string testFilename = "second.stage1";
  489. QString testFilePath = (scanFolderDir / testFilename).AsPosix().c_str();
  490. UnitTestUtils::CreateDummyFile(testFilePath.toUtf8().constData(), "unit test file");
  491. const char* file = m_testFilePath.c_str();
  492. int endStage = 1;
  493. int expectedJobCount = 1;
  494. int expectedFileCount = 1;
  495. // Process the first file
  496. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, file));
  497. QCoreApplication::processEvents();
  498. RunFile(expectedJobCount, expectedFileCount);
  499. // Copy out the job since it will get cleared next time we call RunFile
  500. auto jobListCopy = m_jobDetailsList;
  501. // Process the first file again, this will record 2 jobs for analysis in APM
  502. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, file));
  503. QCoreApplication::processEvents();
  504. RunFile(expectedJobCount, expectedFileCount);
  505. // Add the first job back in
  506. jobListCopy.push_back(m_jobDetailsList[0]);
  507. // Process the 2nd file so there are 2 different files waiting for analysis
  508. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, testFilePath));
  509. QCoreApplication::processEvents();
  510. RunFile(expectedJobCount, expectedFileCount);
  511. jobListCopy.push_back(m_jobDetailsList[0]);
  512. m_jobDetailsList = jobListCopy;
  513. std::stable_sort(
  514. m_jobDetailsList.begin(),
  515. m_jobDetailsList.end(),
  516. [](const AssetProcessor::JobDetails& a, const AssetProcessor::JobDetails& b) -> bool
  517. {
  518. return a.m_jobEntry.m_sourceAssetReference < b.m_jobEntry.m_sourceAssetReference;
  519. });
  520. ASSERT_EQ(m_jobDetailsList.size(), 3);
  521. // Run all 3 jobs through RC. The duplicate should get discarded and marked as cancelled which allows APM to clear it from the analysis
  522. // list
  523. ProcessJob(*m_rc, m_jobDetailsList[0]);
  524. ProcessJob(*m_rc, m_jobDetailsList[1]);
  525. ProcessJob(*m_rc, m_jobDetailsList[2]);
  526. ASSERT_TRUE(m_fileCompiled);
  527. m_assetProcessorManager->CheckFilesToExamine(0);
  528. m_assetProcessorManager->CheckActiveFiles(0);
  529. m_assetProcessorManager->CheckJobEntries(0);
  530. QCoreApplication::processEvents(); // Execute FinishAnalysis
  531. CheckProduct(AZStd::string::format("test.stage%d", endStage + 1).c_str());
  532. // FinishAnalysis should have run and reported no files left waiting for analysis
  533. EXPECT_TRUE(finishedAnalysisOccurred);
  534. EXPECT_EQ(remainingFiles, 0);
  535. EXPECT_EQ(maxWaitingFiles, 1);
  536. }
  537. class AssetProcessorIntermediateAssetTests
  538. : public UnitTests::AssetManagerTestingBase
  539. {
  540. protected:
  541. void DeleteSourceAndValidateNoAdditionalJobs(QString expectedIntermediatePath)
  542. {
  543. // Delete the originating source for the intermediate asset
  544. // Using Qt to remove because AZ::IO::FileIOStream (used by AZ::Utils::WriteFile) doesn't have a file deletion option.
  545. QFile sourceAsset(m_testFilePath.c_str());
  546. EXPECT_TRUE(sourceAsset.remove());
  547. // Purposely call AssessModifiedFile on the intermediate asset before assessing the deleted source asset.
  548. QMetaObject::invokeMethod(
  549. m_assetProcessorManager.get(),
  550. "AssessModifiedFile",
  551. Qt::QueuedConnection,
  552. Q_ARG(QString, expectedIntermediatePath));
  553. QCoreApplication::processEvents();
  554. // In the previous test, after modifying the intermediate asset and calling AssessModifiedFile + processing the event,
  555. // the active file count went to 1 because it had to be processed.
  556. // However, now that the source is deleted, it did not add the file to the list of active filess
  557. m_assetProcessorManager->CheckFilesToExamine(0);
  558. m_assetProcessorManager->CheckActiveFiles(0);
  559. m_assetProcessorManager->CheckJobEntries(0);
  560. // The deleted file has to be assessed before the asset processing step can be done, otherwise it crashes.
  561. m_assetProcessorManager->AssessDeletedFile(m_testFilePath.c_str());
  562. QCoreApplication::processEvents();
  563. // There will now be one file to examine, but nothing active to be processed.
  564. m_assetProcessorManager->CheckFilesToExamine(1);
  565. m_assetProcessorManager->CheckActiveFiles(0);
  566. m_assetProcessorManager->CheckJobEntries(0);
  567. m_assetProcessorManager->ProcessFilesToExamineQueue();
  568. QCoreApplication::processEvents();
  569. // Make sure nothing is left to process - the intermediate asset should never have had a job added
  570. // because the source was deleted at the same time it was modified.
  571. m_assetProcessorManager->CheckFilesToExamine(0);
  572. m_assetProcessorManager->CheckActiveFiles(0);
  573. m_assetProcessorManager->CheckJobEntries(0);
  574. // Nothing should have failed to process.
  575. EXPECT_FALSE(m_fileFailed);
  576. }
  577. };
  578. TEST_F(AssetProcessorIntermediateAssetTests, IntermediateAsset_ModifiedToFail_FailsToProcess)
  579. {
  580. // This validates the setup for IntermediateAsset_SourceDeleted_IntermediateDoesNotReprocess:
  581. // It makes sure that the modified intermediate asset fails to process when the source is not deleted.
  582. // Given:
  583. // A source asset that outputs an intermediate asset
  584. // The intermediate asset outputs a product asset
  585. // When:
  586. // The intermediate asset is modified to fail on being processed
  587. // Then:
  588. // The asset fails to process when processed
  589. using namespace AssetBuilderSDK;
  590. // Given: set up the test
  591. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  592. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  593. ProcessFileMultiStage(2, true);
  594. // When:
  595. // Write text to the intermediate asset file that will cause it to auto-fail the job.
  596. auto expectedIntermediatePath = GetIntermediateAssetsDir() / AZStd::string("test.stage2");
  597. EXPECT_TRUE(AZ::Utils::WriteFile(GetJobProcessFailText(), expectedIntermediatePath.c_str()).IsSuccess());
  598. QMetaObject::invokeMethod(
  599. m_assetProcessorManager.get(),
  600. "AssessModifiedFile",
  601. Qt::QueuedConnection,
  602. Q_ARG(QString, QString(expectedIntermediatePath.c_str())));
  603. QCoreApplication::processEvents();
  604. EXPECT_FALSE(m_fileFailed);
  605. // Verify the file is in the queue to be processed.
  606. // Not totally necessary for this test - the file failed bool flipping tells this test what it needs.
  607. // However, this is done to verify asset processing state to compare to other tests.
  608. // If this test verifies the file is in the queue here, then other tests can check the file is not queued.
  609. m_assetProcessorManager->CheckFilesToExamine(0);
  610. m_assetProcessorManager->CheckActiveFiles(1);
  611. m_assetProcessorManager->CheckJobEntries(0);
  612. // Then: The file fails to process.
  613. ProcessSingleStep(1, 1, 0, /*expectSuccess*/ false);
  614. EXPECT_TRUE(m_fileFailed);
  615. m_assetProcessorManager->CheckFilesToExamine(0);
  616. m_assetProcessorManager->CheckActiveFiles(0);
  617. m_assetProcessorManager->CheckJobEntries(0);
  618. }
  619. TEST_F(AssetProcessorIntermediateAssetTests, IntermediateAsset_SourceDeleted_IntermediateDoesNotReprocess)
  620. {
  621. // This is a regression test.
  622. // There was a situation where a change to the material system was not compatible with old intermediate assets.
  623. // The source assets for those intermediate assets had been deleted, so it was a surprise and unexpected
  624. // when the stale intermediate assets were failing to process, causing Jenkins jobs to fail due to asset failures.
  625. // What was expected was that, because the source asset that generated those intermediate assets had been removed,
  626. // the Asset Processor would no longer attempt to process the intermediate assets.
  627. // Given:
  628. // A source asset that outputs an intermediate asset
  629. // The intermediate asset outputs a product asset
  630. // When:
  631. // While the asset processor is not running, so these operations are picked up at the same time:
  632. // The source asset is deleted
  633. // A change has been made that would cause the intermediate asset to reprocess
  634. // The next time the intermediate asset reprocesses, it will fail to process
  635. // Then:
  636. // The intermediate asset should not reprocess, because the source asset was deleted
  637. using namespace AssetBuilderSDK;
  638. // Given: set up the test
  639. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  640. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  641. ProcessFileMultiStage(2, true);
  642. auto expectedIntermediatePath = GetIntermediateAssetsDir() / AZStd::string("test.stage2");
  643. // When:
  644. // Write text to the intermediate asset file that would cause it to auto-fail the job if it were processed again.
  645. EXPECT_TRUE(AZ::Utils::WriteFile(GetJobProcessFailText(), expectedIntermediatePath.c_str()).IsSuccess());
  646. // When: The source is deleted.
  647. // Then: No additional jobs are created for the modified intermediate asset, and no jobs fail.
  648. DeleteSourceAndValidateNoAdditionalJobs(expectedIntermediatePath.c_str());
  649. }
  650. TEST_F(AssetProcessorIntermediateAssetTests, NestedIntermediateAsset_SourceDeleted_IntermediateDoesNotReprocess)
  651. {
  652. // This test is a variant of IntermediateAsset_SourceDeleted_IntermediateDoesNotReprocess, but
  653. // it tests with a deeply collection of intermediate assets. Source -> Intermediate A -> Intermediate B -> Intermediate C.
  654. // It verifies that deleting the root source, and making a change that causes these deeper intermediate assets to reprocess
  655. // will not end up re-processing the deep intermediate assets, and instead skip processing them because the root source is gone.
  656. using namespace AssetBuilderSDK;
  657. // Given: a deeply nested set of intermediate assets.
  658. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  659. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  660. CreateBuilder("stage3", "*.stage3", "stage4", true, ProductOutputFlags::IntermediateAsset);
  661. CreateBuilder("stage4", "*.stage4", "stage5", true, ProductOutputFlags::IntermediateAsset);
  662. CreateBuilder("stage5", "*.stage5", "stage6", false, ProductOutputFlags::ProductAsset);
  663. ProcessFileMultiStage(5, true);
  664. // When:
  665. // Write text to the intermediate asset file that would cause it to auto-fail the job if it were processed again.
  666. auto expectedIntermediatePath = GetIntermediateAssetsDir() / AZStd::string("test.stage5");
  667. EXPECT_TRUE(AZ::Utils::WriteFile(GetJobProcessFailText(), expectedIntermediatePath.c_str()).IsSuccess());
  668. // When: The source is deleted.
  669. // Then: No additional jobs are created for the modified intermediate asset, and no jobs fail.
  670. DeleteSourceAndValidateNoAdditionalJobs(expectedIntermediatePath.c_str());
  671. }
  672. TEST_F(AssetProcessorIntermediateAssetTests, IntermediateAsset_SourceNoLongerEmitsJobIntermediateWouldFailToProcess_NoFailure)
  673. {
  674. // This is a regression test for this situation:
  675. // 1. A source asset would emit one of two different jobs from CreateJobs, based on data in the source asset itself. One of these emits intermediate assets, but not the other.
  676. // 2. The AssetProcessor would run once, and the source asset would run with the job that emits intermediate assets.
  677. // 3. The source asset was changed so that it no longer emits the job that creates the intermediate asset, it emits a different job with different products.
  678. // 4. The Asset Processor is run again.
  679. // Expected, and what this test verifies: The intermediate asset is removed, because it is no longer a product of the source asset.
  680. // Before the regression fix: The intermediate asset was not being removed.
  681. using namespace AssetBuilderSDK;
  682. // Given:
  683. // Asset builder that emits different jobs based on information in the source asset.
  684. // A source asset that initially is marked to emit an intermediate asset product.
  685. // This chain of assets (source and intermediate) processed without error or issue.
  686. bool outputIntermediateProduct = true;
  687. m_builderInfoHandler.CreateBuilderDesc(
  688. "stage1",
  689. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  690. { AssetBuilderPattern{ "*.stage1", AssetBuilderPattern::Wildcard } },
  691. [&outputIntermediateProduct]([[maybe_unused]] const CreateJobsRequest& request, CreateJobsResponse& response)
  692. {
  693. // The first time this job is run - create an intermediate asset job.
  694. if (outputIntermediateProduct)
  695. {
  696. response.m_createJobOutputs.push_back(JobDescriptor{ "fingerprint", "stage1 - Intermediate", CommonPlatformName });
  697. }
  698. // The second time this job is run - Create a non-intermediate asset, just regular product job.
  699. else
  700. {
  701. for (const auto& platform : request.m_enabledPlatforms)
  702. {
  703. response.m_createJobOutputs.push_back(JobDescriptor{
  704. "fingerprint",
  705. "stage1 - Product",
  706. platform.m_identifier.c_str() });
  707. }
  708. }
  709. response.m_result = CreateJobsResultCode::Success;
  710. },
  711. [&outputIntermediateProduct](const ProcessJobRequest& request, ProcessJobResponse& response)
  712. {
  713. AZ::IO::Path outputFile = request.m_sourceFile;
  714. AZStd::string outputExtension = "stage2";
  715. if (!outputIntermediateProduct)
  716. {
  717. outputExtension = "stage2_product";
  718. }
  719. outputFile.ReplaceExtension(outputExtension.c_str());
  720. AZ::IO::LocalFileIO::GetInstance()->Copy(
  721. request.m_fullPath.c_str(), (AZ::IO::Path(request.m_tempDirPath) / outputFile).c_str());
  722. auto product = JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName(outputExtension.c_str()), AssetSubId };
  723. if (outputIntermediateProduct)
  724. {
  725. product.m_outputFlags = ProductOutputFlags::IntermediateAsset;
  726. }
  727. else
  728. {
  729. product.m_outputFlags = ProductOutputFlags::ProductAsset;
  730. }
  731. product.m_dependenciesHandled = true;
  732. response.m_outputProducts.push_back(product);
  733. response.m_resultCode = ProcessJobResult_Success;
  734. },
  735. "fingerprint");
  736. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  737. ProcessFileMultiStage(2, true);
  738. AZStd::string intermediateAssetPath = MakePath("test.stage2", true);
  739. // Verify the intermediate asset exists
  740. EXPECT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(intermediateAssetPath.c_str()));
  741. // When:
  742. // The source asset has been modified to no longer emit the intermediate asset as a product, and emit a different product.
  743. // This chain of assets is processed again.
  744. // Modify the source asset, so it shows up as needing to be reprocessed.
  745. EXPECT_TRUE(AZ::Utils::WriteFile("Arbitrary text to mark this file as modified.", m_testFilePath.c_str()).IsSuccess());
  746. // Mark the source asset to no longer emit the intermediate product asset
  747. outputIntermediateProduct = false;
  748. // Call AssessModifiedFile on the source asset.
  749. m_assetProcessorManager->AssessModifiedFile(m_testFilePath.c_str());
  750. // There is one active file, because it has been modified.
  751. m_assetProcessorManager->CheckFilesToExamine(0);
  752. m_assetProcessorManager->CheckActiveFiles(1);
  753. m_assetProcessorManager->CheckJobEntries(0);
  754. // Assess the modified file
  755. QCoreApplication::processEvents();
  756. // The file has been moved from active, to the examine list, after assessing it.
  757. m_assetProcessorManager->CheckFilesToExamine(1);
  758. m_assetProcessorManager->CheckActiveFiles(0);
  759. m_assetProcessorManager->CheckJobEntries(0);
  760. // Process the file, which triggers CheckMissingJobs to be called, which actually deletes the no longer emitted file.
  761. // This doesn't call ProcessSingleStep because the files to examine and active files won't match what ProcessSingleStep expects.
  762. // m_jobDetailsList lets this test verify the job ran that was expected to run.
  763. m_jobDetailsList.clear();
  764. m_fileCompiled = false;
  765. m_fileFailed = false;
  766. QCoreApplication::processEvents(); // execute ProcessFilesToExamineQueue
  767. QCoreApplication::processEvents(); // execute CheckForIdle
  768. ASSERT_EQ(m_jobDetailsList.size(), 1);
  769. ProcessJob(*m_rc, m_jobDetailsList[0]);
  770. ASSERT_TRUE(m_fileCompiled);
  771. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  772. // Then:
  773. // Asset processing completes and the intermediate asset is deleted, because it's no longer a product of this source asset.
  774. // Make sure nothing is left to process
  775. m_assetProcessorManager->CheckFilesToExamine(0);
  776. m_assetProcessorManager->CheckActiveFiles(0);
  777. m_assetProcessorManager->CheckJobEntries(0);
  778. // Nothing should have failed to process.
  779. EXPECT_FALSE(m_fileFailed);
  780. // The intermediate asset should be deleted and gone, because CheckMissingJobs removed it for no longer being a product
  781. // of any source asset.
  782. EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(intermediateAssetPath.c_str()));
  783. }
  784. // Used for tests that work with source dependencies and intermediate assets.
  785. class AssetProcessorIntermediateAssetSourceDependencyTests
  786. : public AssetProcessorIntermediateAssetTests
  787. {
  788. public:
  789. // Helper function - sets up the paths to files used by this test.
  790. void GenerateAssetPaths()
  791. {
  792. // First, prep the paths and file names in use for the test.
  793. m_firstFileName = AZ::IO::Path(m_firstFileNameNoExtension.c_str()).ReplaceExtension(m_firstFileExtension.c_str());
  794. m_firstFilePath = AZ::IO::Path(m_scanfolder.m_scanFolder).Append(m_firstFileName);
  795. m_secondFileName = AZ::IO::Path(m_secondFileNameNoExtension.c_str()).ReplaceExtension(m_secondFileExtension.c_str());
  796. m_secondFilePath = AZ::IO::Path(m_scanfolder.m_scanFolder).Append(m_secondFileName);
  797. m_intermediateFileName = AZ::IO::Path(m_firstFileNameNoExtension.c_str()).ReplaceExtension(m_intermediateExtension.c_str());
  798. m_intermediateAssetPath = MakePath(m_intermediateFileName.c_str(), true);
  799. m_firstProductPath = MakePath(AZ::IO::Path(m_firstFileNameNoExtension.c_str()).ReplaceExtension(m_firstProductExtension.c_str()).c_str(), false);
  800. // Store the path to the product asset, so that the test can examine the contents of the product asset.
  801. m_secondProductPath =
  802. MakePath(AZ::IO::Path(m_secondFileNameNoExtension.c_str()).ReplaceExtension(m_SecondProductExtension.c_str()).c_str(), false);
  803. }
  804. // Helper function - generates the initial files ued by this test.
  805. void CreateTestAssets()
  806. {
  807. AZ::Utils::WriteFile("unit test file", m_firstFilePath.c_str());
  808. AZ::Utils::WriteFile("unit test file", m_secondFilePath.c_str());
  809. }
  810. // Creates three builders:
  811. // Source A - Outputs Intermediate A
  812. // Intermediate A - Outputs Product A
  813. // Source B - Outputs Product B, has a source dependency on Intermediate A.
  814. void CreateBuilders()
  815. {
  816. using namespace AssetBuilderSDK;
  817. // Source A's builder, this outputs the Intermediate asset.
  818. CreateBuilder(
  819. m_firstFileExtension.c_str(),
  820. MakeWildcardForExtension(m_firstFileExtension).c_str(),
  821. m_intermediateExtension.c_str(),
  822. true,
  823. ProductOutputFlags::IntermediateAsset);
  824. // Intermediate A's builder, this outputs a product asset.
  825. CreateBuilder(
  826. m_intermediateExtension.c_str(),
  827. MakeWildcardForExtension(m_intermediateExtension).c_str(),
  828. m_firstProductExtension.c_str(),
  829. false,
  830. ProductOutputFlags::ProductAsset);
  831. // Source B's builder. This builder emits the intermediate A as a dependency.
  832. m_builderInfoHandler.CreateBuilderDesc(
  833. QString(m_secondFileExtension.c_str()),
  834. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  835. { AssetBuilderPattern{ MakeWildcardForExtension(m_secondFileExtension), AssetBuilderPattern::Wildcard } },
  836. [this]([[maybe_unused]] const CreateJobsRequest& request, CreateJobsResponse& response)
  837. {
  838. for (const auto& platform : request.m_enabledPlatforms)
  839. {
  840. response.m_createJobOutputs.push_back(
  841. JobDescriptor{ "fingerprint", "Source B - Product", platform.m_identifier.c_str() });
  842. // Create the dependency on the path to the intermediate asset.
  843. response.m_createJobOutputs.back().m_jobDependencyList.push_back(JobDependency(
  844. m_intermediateExtension.c_str(),
  845. platform.m_identifier,
  846. JobDependencyType::Order,
  847. { m_intermediateAssetPath.c_str(),
  848. AZ::Uuid::CreateNull(),
  849. AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute }));
  850. }
  851. response.m_result = CreateJobsResultCode::Success;
  852. },
  853. [this](const ProcessJobRequest& request, ProcessJobResponse& response)
  854. {
  855. AZ::IO::Path outputFile = request.m_sourceFile;
  856. outputFile.ReplaceExtension(m_SecondProductExtension.c_str());
  857. outputFile = AZ::IO::Path(request.m_tempDirPath).Append(outputFile);
  858. // Write if the intermediate exists or not to the product asset.
  859. bool intermediateProductExists = (AZ::IO::FileIOBase::GetInstance()->Exists(m_firstProductPath.c_str()));
  860. AZStd::string toWrite = m_intermediateProductExistsString;
  861. if (!intermediateProductExists)
  862. {
  863. toWrite = m_intermediateProductDoesNotExistString;
  864. }
  865. AZ::Utils::WriteFile(toWrite.c_str(), outputFile.c_str());
  866. auto product =
  867. JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName(m_SecondProductExtension.c_str()), AssetSubId };
  868. product.m_outputFlags = ProductOutputFlags::ProductAsset;
  869. product.m_dependenciesHandled = true;
  870. response.m_outputProducts.push_back(product);
  871. response.m_resultCode = ProcessJobResult_Success;
  872. },
  873. "fingerprint");
  874. }
  875. void SetUp() override
  876. {
  877. AssetProcessorIntermediateAssetTests::SetUp();
  878. GenerateAssetPaths();
  879. CreateTestAssets();
  880. // Jobs with dependencies need those dependencies to have updated the asset catalog before the job with the dependency runs.
  881. SetCatalogToUpdateOnJobCompletion();
  882. CreateBuilders();
  883. }
  884. void TearDown() override
  885. {
  886. AssetProcessorIntermediateAssetTests::TearDown();
  887. }
  888. protected:
  889. AZStd::string MakeWildcardForExtension(const AZStd::string& extension)
  890. {
  891. return AZStd::string::format("*.%.*s", AZ_STRING_ARG(extension));
  892. }
  893. AZStd::string m_firstFileExtension = "a_source";
  894. AZStd::string m_firstFileNameNoExtension = "firstfile";
  895. AZ::IO::Path m_firstFileName;
  896. AZ::IO::Path m_firstFilePath;
  897. AZStd::string m_intermediateExtension = "a_intermediate";
  898. AZ::IO::Path m_intermediateFileName;
  899. AZ::IO::Path m_intermediateAssetPath;
  900. AZStd::string m_firstProductExtension = "a_product";
  901. AZStd::string m_firstProductPath;
  902. AZStd::string m_secondFileExtension = "b_source";
  903. AZStd::string m_secondFileNameNoExtension = "secondfile";
  904. AZ::IO::Path m_secondFileName;
  905. AZ::IO::Path m_secondFilePath;
  906. AZStd::string m_SecondProductExtension = "b_product";
  907. AZStd::string m_secondProductPath;
  908. AZStd::string m_intermediateProductExistsString = "Intermediate product exists.";
  909. AZStd::string m_intermediateProductDoesNotExistString = "Intermediate product does not exist.";
  910. };
  911. TEST_F(AssetProcessorIntermediateAssetSourceDependencyTests, SourceDependencyIsIntermediateAsset_NotInitiallyAvailable_JobsWaitsForIntermediateJobToExistAndRun)
  912. {
  913. // This is a regression test for a situation where Job B depends on an intermediate asset job (Job A -> Intermediate Job A).
  914. // Before this was fixed, Job B would be queued before Job A and Job B was unaware that Job A created Intermediate Job A.
  915. // After the fix, jobs with missing dependencies are queued to run later than other jobs, and Common platform jobs (Job A)
  916. // are prioritized in the queue.
  917. // Setup:
  918. // Builder for Source A is created. Processes "a_source" assets into "a_intermediate" assets.
  919. // Builder for Intermediate A is created. Processes "a_intermediate" into "a_product" assets.
  920. // Builder for Source B is created. Emits a job dependency specifically on the Intermediate A job in this test setup.
  921. // A source file for A and B are created.
  922. // Test:
  923. // Jobs are both queued for Source A and Source B.
  924. // Source A job runs first, because it does not have a missing dependency.
  925. // Call asset processing updating in a specific order, out of processEvents order, because
  926. // of a race condition where Intermediate A hasn't been found and had a job created by the Asset Processor
  927. // before Job B comes up as next in the queue, causing Job B to report that it's running before its dependency is resolved.
  928. // Instead, ScheduleNextUpdate -> ProcessFilesToExamineQueue -> CheckForIdle will make sure that the Intermediate A job is emitted.
  929. // Verify that there are two jobs ready to submitted to the resource compiler: A new Job B with the dependency resolved, and Intermediate A.
  930. // Process assets twice.
  931. // Verify that the jobs run in order, and that the product of Intermediate A is available on disk during the processing of Job B.
  932. QMetaObject::invokeMethod(
  933. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_firstFilePath.c_str()));
  934. QMetaObject::invokeMethod(
  935. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_secondFilePath.c_str()));
  936. QCoreApplication::processEvents();
  937. m_fileCompiled = false;
  938. m_fileFailed = false;
  939. RunFile(2, 2);
  940. // Queue both jobs at the same time, so that RCQueueSortModel.cpp will order these jobs and run them in order.
  941. // TODO: Note that the PC job (asset B) will always run before the Common job (asset A) because the system
  942. // purposely prioritizes host platform jobs first.
  943. ASSERT_EQ(m_jobDetailsList.size(), 2);
  944. // One of the two jobs should have the missing source dependency flagged for follow up.
  945. EXPECT_NE(m_jobDetailsList[0].m_hasMissingSourceDependency, m_jobDetailsList[1].m_hasMissingSourceDependency);
  946. m_rc->JobSubmitted(m_jobDetailsList[0]);
  947. m_rc->JobSubmitted(m_jobDetailsList[1]);
  948. m_jobDetailsList.clear();
  949. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform("pc"), 1);
  950. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform(AssetBuilderSDK::CommonPlatformName), 1);
  951. UnitTests::JobSignalReceiver receiver;
  952. // Process the first asset in the queue, which will be the job for A, because B had a missing dependency and was made lower priority.
  953. m_rc->DispatchJobsImpl();
  954. // Pause dispatching. In this test scenario, with only two assets queued,
  955. // it's likely that Job B will finish before Job B is re-issued due to the newly
  956. // updated source asset.
  957. // This happens frequently when step-through debugging this function with breakpoints.
  958. // This can also happen when running many tests in parallel: There are a lot of async calls
  959. // in this test, and depending on the state of the machine running this test, those may resolve
  960. // in a different order or timing. Pausing dispatching until the moment that dispatchJobsImpl is called
  961. // mitigates this: Jobs only execute when this test needs them to.
  962. m_rc->SetDispatchPaused(true);
  963. receiver.WaitForFinish();
  964. QCoreApplication::processEvents(); // RCJob::Finished : Once more to trigger the JobFinished event
  965. QCoreApplication::processEvents(); // RCController::FinishJob : Again to trigger the Finished event
  966. // Product B shouldn't exist yet because A was processed first.
  967. EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(m_secondProductPath.c_str()));
  968. // Emit that A was finished processing.
  969. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  970. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform("pc"), 1);
  971. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform(AssetBuilderSDK::CommonPlatformName), 0);
  972. // Manually call each step to make sure the newly created intermediate asset gets discovered and queued
  973. // before the pending job with a missing dependency is run, because the queue has one entry right now.
  974. // Otherwise, it's likely the resource compiler will pick up the next job before the intermediate job is processed.
  975. // Which means the missing dependency warning will fire off and cause this test to fail.
  976. // This is a race condition in asset processing: If the last asset to process fills the missing dependency of the
  977. // next job in the queue, then there's a brief period where intermediate asset hasn't been discovered yet and isn't queued,
  978. // so the next job, with the missing dependency will run. This is mitigated by having the Common platform jobs run
  979. // before non-Common platform jobs. Additional mitigation isn't currently necessary.
  980. m_assetProcessorManager->ScheduleNextUpdate();
  981. m_assetProcessorManager->ProcessFilesToExamineQueue();
  982. // Call CheckForIdle twice, once for each pending asset.
  983. m_assetProcessorManager->CheckForIdle();
  984. m_assetProcessorManager->CheckForIdle();
  985. // Make sure events are dispatched and m_jobDetailsList is updated with both jobs.
  986. // The queue should now be the job to process the intermediate asset, and the re-created Job B, which no longer has a missing dependency.
  987. ASSERT_EQ(m_jobDetailsList.size(), 2);
  988. // Neither job in the queue should have the missing source dependency flag.
  989. EXPECT_FALSE(m_jobDetailsList[0].m_hasMissingSourceDependency);
  990. EXPECT_FALSE(m_jobDetailsList[1].m_hasMissingSourceDependency);
  991. m_rc->JobSubmitted(m_jobDetailsList[0]);
  992. m_rc->JobSubmitted(m_jobDetailsList[1]);
  993. m_jobDetailsList.clear();
  994. // The platform is for the target output, not for the platform the source was on, so the intermediate asset job will
  995. // have the PC platform.
  996. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform("pc"), 2);
  997. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform(AssetBuilderSDK::CommonPlatformName), 0);
  998. // The original Job B, with the missing dependency, was canceled.
  999. // Verify the pending job list matches the model's queue.
  1000. // Canceling jobs doesn't guarantee the pending count will be updated, but it does guarantee the row count
  1001. // of the model is updated. This check verifies that the call to cancel the job was done correctly, and updated
  1002. // the pending list.
  1003. AssetProcessor::RCQueueSortModel& sortModel = m_rc->GetRCQueueSortModel();
  1004. EXPECT_EQ(sortModel.rowCount(), m_rc->NumberOfPendingJobsPerPlatform("pc"));
  1005. m_rc->SetDispatchPaused(false);
  1006. m_rc->DispatchJobsImpl();
  1007. m_rc->SetDispatchPaused(true);
  1008. receiver.WaitForFinish();
  1009. QCoreApplication::processEvents(); // RCJob::Finished : Once more to trigger the JobFinished event
  1010. QCoreApplication::processEvents(); // RCController::FinishJob : Again to trigger the Finished event
  1011. // Mark this asset as processed, so AP will move on to the next steps.
  1012. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  1013. // Need to call process events multiple times, to get the job details list populated with the intermediate asset job
  1014. // Due to timing of this test, these events may end up running in different process events calls.
  1015. // updates a lot of general AssetProcessor systems:
  1016. // AssetProcessorManager::ScheduleNextUpdate, AssetProcessorManager::ProcessFilesToExamineQueue,
  1017. // AssetProcessorManager::QueueIdleCheck, AssetProcessorManager::ProcessBuilders, and more.
  1018. // Also updates several AssetProcessor systems that the previous step updates,
  1019. // but the main events this is run for is two calls to AssetProcessorManager::AssetToProcess
  1020. // which puts Cache/Intermediate Assets/firstfile.a_intermediate and secondfile.b_source in m_jobDetailsList
  1021. // Make sure the job requests are populated and ready to go, without this, RCJob::PopulateProcessJobRequest sometimes crashes accessing job info.
  1022. QCoreApplication::processEvents();
  1023. QCoreApplication::processEvents();
  1024. QCoreApplication::processEvents();
  1025. // Verify that the job for the second asset didn't process yet, because it has a job dependency on the intermediate asset job.
  1026. EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(m_secondProductPath.c_str()));
  1027. // Emit that the intermediate job was finished processing.
  1028. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  1029. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform("pc"), 1);
  1030. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform(AssetBuilderSDK::CommonPlatformName), 0);
  1031. // Make sure all remaning non-canceled jobs are processed.
  1032. m_rc->SetDispatchPaused(false);
  1033. WaitForNextJobToProcess(receiver);
  1034. // Verify the final product is marked as existing.
  1035. auto readResult = AZ::Utils::ReadFile<AZStd::string>(m_secondProductPath.c_str(), AZStd::numeric_limits<size_t>::max());
  1036. EXPECT_TRUE(readResult.IsSuccess());
  1037. EXPECT_EQ(readResult.GetValue().compare(m_intermediateProductExistsString), 0);
  1038. // Examine the queue directly : There should be nothing left in the pending job queue, or the sort model's row count.
  1039. EXPECT_EQ(m_rc->NumberOfPendingJobsPerPlatform("pc"), 0);
  1040. EXPECT_EQ(sortModel.GetNextPendingJob(), nullptr);
  1041. // The canceled job was removed from the actual sort model, it just wasn't removed from the pending per platform list.
  1042. EXPECT_EQ(sortModel.rowCount(), 0);
  1043. }
  1044. TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToDatabase)
  1045. {
  1046. // This tests the JobDiagnosticTracker: Warnings/errors reported to it should be recorded in the database when AssetProcessed is fired and able to be retrieved when querying job status
  1047. using namespace AzToolsFramework::AssetSystem;
  1048. using namespace AssetProcessor;
  1049. QString relFileName("assetProcessorManagerTest.txt");
  1050. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
  1051. QString watchFolder = m_assetRootDir.absoluteFilePath("subfolder1");
  1052. JobEntry entry;
  1053. entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder, relFileName);
  1054. entry.m_jobKey = "txt";
  1055. entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
  1056. entry.m_jobRunKey = 1;
  1057. UnitTestUtils::CreateDummyFile(m_normalizedCacheRootDir.absoluteFilePath("pc/outputfile.txt"));
  1058. AssetBuilderSDK::ProcessJobResponse jobResponse;
  1059. jobResponse.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1060. jobResponse.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("outputfile.txt"));
  1061. JobDiagnosticRequestBus::Broadcast(&JobDiagnosticRequestBus::Events::RecordDiagnosticInfo, entry.m_jobRunKey, JobDiagnosticInfo(11, 22));
  1062. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, entry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, jobResponse));
  1063. // let events bubble through:
  1064. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1065. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1066. AZ::Uuid uuid = AssetUtilities::GetSourceUuid(entry.m_sourceAssetReference).GetValue();
  1067. AssetJobsInfoRequest request;
  1068. request.m_assetId = AZ::Data::AssetId(uuid, 0);
  1069. request.m_escalateJobs = false;
  1070. AssetJobsInfoResponse response;
  1071. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(request, response);
  1072. EXPECT_TRUE(response.m_isSuccess);
  1073. EXPECT_EQ(1, response.m_jobList.size());
  1074. ASSERT_GT(response.m_jobList.size(), 0); // Assert on this to exit early if needed, otherwise indexing m_jobList later will crash.
  1075. EXPECT_EQ(JobStatus::Completed, response.m_jobList[0].m_status);
  1076. EXPECT_STRCASEEQ(relFileName.toUtf8().data(), response.m_jobList[0].m_sourceFile.c_str());
  1077. ASSERT_EQ(response.m_jobList[0].m_warningCount, 11);
  1078. ASSERT_EQ(response.m_jobList[0].m_errorCount, 22);
  1079. ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0);
  1080. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
  1081. ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
  1082. }
  1083. TEST_F(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles)
  1084. {
  1085. using namespace AssetProcessor;
  1086. static constexpr char folderPathNoScanfolder[] = "folder/folder/foldertest.txt";
  1087. static constexpr char folderPath[] = "subfolder1/folder/folder/foldertest.txt";
  1088. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath(folderPath));
  1089. auto scanFolderInfo = m_config->GetScanFolderByPath(m_assetRootDir.absoluteFilePath("subfolder1"));
  1090. ASSERT_TRUE(scanFolderInfo != nullptr);
  1091. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry(
  1092. scanFolderInfo->ScanFolderID(),
  1093. folderPathNoScanfolder,
  1094. AZ::Uuid::CreateRandom(),
  1095. /*analysisFingerprint - arbitrary*/ "abcdefg");
  1096. m_assetProcessorManager->m_stateData->SetSource(sourceEntry);
  1097. int count = 0;
  1098. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [&count](const SourceAssetReference& file)
  1099. {
  1100. if (QString(file.RelativePath().c_str()).compare(folderPathNoScanfolder, Qt::CaseInsensitive) == 0)
  1101. {
  1102. count++;
  1103. }
  1104. });
  1105. m_isIdling = false;
  1106. // tell the APM about the files:
  1107. m_assetProcessorManager->AssessAddedFile(m_assetRootDir.absoluteFilePath(folderPath));
  1108. ASSERT_TRUE(BlockUntilIdle(5000));
  1109. EXPECT_TRUE(QDir(m_assetRootDir.absoluteFilePath("subfolder1/folder")).removeRecursively());
  1110. m_isIdling = false;
  1111. m_assetProcessorManager->AssessDeletedFile(m_assetRootDir.absoluteFilePath("subfolder1/folder"));
  1112. ASSERT_TRUE(BlockUntilIdle(5000));
  1113. EXPECT_EQ(1, count);
  1114. }
  1115. TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDFailure)
  1116. {
  1117. using namespace AzToolsFramework::AssetSystem;
  1118. using namespace AssetProcessor;
  1119. QString absolutePath = m_assetRootDir.absoluteFilePath("assetProcessorManagerTestFailed.txt");
  1120. AZ::Uuid uuid = AssetUtilities::GetSourceUuid(SourceAssetReference(absolutePath.toUtf8().data())).GetValue();
  1121. AssetJobsInfoRequest request;
  1122. request.m_assetId = AZ::Data::AssetId(uuid, 0);
  1123. request.m_escalateJobs = false;
  1124. AssetJobsInfoResponse response;
  1125. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(request, response);
  1126. ASSERT_TRUE(response.m_isSuccess == false); //expected result should be false because AP does not know about this asset
  1127. ASSERT_TRUE(response.m_jobList.size() == 0);
  1128. }
  1129. TEST_F(AssetProcessorManagerTest, UnitTestForCancelledJob)
  1130. {
  1131. using namespace AzToolsFramework::AssetSystem;
  1132. using namespace AssetProcessor;
  1133. QString relFileName("assetProcessorManagerTest.txt");
  1134. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
  1135. JobEntry entry;
  1136. entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1"), relFileName);
  1137. entry.m_jobKey = "txt";
  1138. entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
  1139. entry.m_jobRunKey = 1;
  1140. AZ::Uuid sourceUUID = AssetUtilities::GetSourceUuid(entry.m_sourceAssetReference).GetValue();
  1141. bool sourceFound = false;
  1142. //Checking the response of the APM when we cancel a job in progress
  1143. m_assetProcessorManager->OnJobStatusChanged(entry, JobStatus::Queued);
  1144. m_assetProcessorManager->OnJobStatusChanged(entry, JobStatus::InProgress);
  1145. ASSERT_TRUE(m_assetProcessorManager->CheckJobKeyToJobRunKeyMap(entry.m_jobKey.toUtf8().data()));
  1146. m_assetProcessorManager->AssetCancelled(entry);
  1147. ASSERT_FALSE(m_assetProcessorManager->CheckJobKeyToJobRunKeyMap(entry.m_jobKey.toUtf8().data()));
  1148. ASSERT_TRUE(m_assetProcessorManager->GetDatabaseConnection()->QuerySourceBySourceGuid(sourceUUID, [&]([[maybe_unused]] AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source)
  1149. {
  1150. sourceFound = true;
  1151. return false;
  1152. }));
  1153. ASSERT_FALSE(sourceFound);
  1154. }
  1155. // if the function to compute builder dirtiness is not called, we should always be dirty
  1156. TEST_F(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty)
  1157. {
  1158. using namespace AzToolsFramework::AssetSystem;
  1159. using namespace AssetProcessor;
  1160. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1161. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1162. }
  1163. class MockBuilderResponder
  1164. : public AssetProcessor::AssetBuilderInfoBus::Handler
  1165. {
  1166. public:
  1167. MockBuilderResponder() {}
  1168. virtual ~MockBuilderResponder() {}
  1169. //! AssetProcessor::AssetBuilderInfoBus Interface
  1170. void GetMatchingBuildersInfo(const AZStd::string& /*assetPath*/, AssetProcessor::BuilderInfoList& /*builderInfoList*/) override
  1171. {
  1172. // not used
  1173. ASSERT_TRUE(false) << "This function should not be called";
  1174. return;
  1175. }
  1176. void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& builderInfoList) override
  1177. {
  1178. builderInfoList = m_assetBuilderDescs;
  1179. }
  1180. ////////////////////////////////////////////////
  1181. AssetProcessor::BuilderInfoList m_assetBuilderDescs;
  1182. void AddBuilder(const char* name, const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& patterns, const AZ::Uuid& busId, int version, const char* fingerprint)
  1183. {
  1184. AssetBuilderSDK::AssetBuilderDesc newDesc;
  1185. newDesc.m_name = name;
  1186. newDesc.m_patterns = patterns;
  1187. newDesc.m_busId = busId;
  1188. newDesc.m_version = version;
  1189. newDesc.m_analysisFingerprint = fingerprint;
  1190. m_assetBuilderDescs.emplace_back(AZStd::move(newDesc));
  1191. }
  1192. };
  1193. struct BuilderDirtiness : public AssetProcessorManagerTest
  1194. {
  1195. void SetUp() override
  1196. {
  1197. AssetProcessorManagerTest::SetUp();
  1198. // Disconnect the mock application manager, our MockBuilderResponder will handle builder registration instead
  1199. m_mockApplicationManager->BusDisconnect();
  1200. m_mockBuilderResponder.BusConnect();
  1201. }
  1202. void TearDown() override
  1203. {
  1204. m_mockBuilderResponder.BusDisconnect();
  1205. AssetProcessorManagerTest::TearDown();
  1206. }
  1207. MockBuilderResponder m_mockBuilderResponder;
  1208. };
  1209. // if our database was empty before, all builders should be dirty
  1210. // note that this requires us to actually register a builder using the mock.
  1211. TEST_F(BuilderDirtiness, BuilderDirtiness_EmptyDatabase_AllDirty)
  1212. {
  1213. using namespace AzToolsFramework::AssetSystem;
  1214. using namespace AssetProcessor;
  1215. using namespace AssetBuilderSDK;
  1216. m_mockBuilderResponder.AddBuilder(
  1217. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1218. "fingerprint1");
  1219. m_mockBuilderResponder.AddBuilder(
  1220. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1221. "fingerprint2");
  1222. m_assetProcessorManager->ComputeBuilderDirty();
  1223. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1224. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1225. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 2);
  1226. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[0].m_busId));
  1227. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[1].m_busId));
  1228. m_mockBuilderResponder.BusDisconnect();
  1229. }
  1230. // if we have the same set of builders the next time, nothing should register as changed.
  1231. TEST_F(BuilderDirtiness, BuilderDirtiness_SameAsLastTime_NoneDirty)
  1232. {
  1233. using namespace AzToolsFramework::AssetSystem;
  1234. using namespace AssetProcessor;
  1235. using namespace AssetBuilderSDK;
  1236. m_mockBuilderResponder.AddBuilder(
  1237. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1238. "fingerprint1");
  1239. m_mockBuilderResponder.AddBuilder(
  1240. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1241. "fingerprint2");
  1242. m_assetProcessorManager->ComputeBuilderDirty();
  1243. // now we retrigger the dirty computation, so that nothing has changed:
  1244. m_assetProcessorManager->ComputeBuilderDirty();
  1245. EXPECT_FALSE(m_assetProcessorManager->m_anyBuilderChange);
  1246. EXPECT_FALSE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1247. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 0);
  1248. m_mockBuilderResponder.BusDisconnect();
  1249. }
  1250. // when a new builder appears, the new builder should be dirty,
  1251. TEST_F(BuilderDirtiness, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty)
  1252. {
  1253. using namespace AzToolsFramework::AssetSystem;
  1254. using namespace AssetProcessor;
  1255. using namespace AssetBuilderSDK;
  1256. m_mockBuilderResponder.AddBuilder(
  1257. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1258. "fingerprint1");
  1259. m_assetProcessorManager->ComputeBuilderDirty();
  1260. m_mockBuilderResponder.AddBuilder(
  1261. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1262. "fingerprint2");
  1263. m_assetProcessorManager->ComputeBuilderDirty();
  1264. // one new builder should have been dirty:
  1265. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1266. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1267. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1268. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[1].m_busId));
  1269. m_mockBuilderResponder.BusDisconnect();
  1270. }
  1271. // when an existing builder disappears there are no dirty builders, but the booleans
  1272. // that track dirtiness should be correct:
  1273. TEST_F(BuilderDirtiness, BuilderDirtiness_FewerThanLastTime_Dirty)
  1274. {
  1275. using namespace AzToolsFramework::AssetSystem;
  1276. using namespace AssetProcessor;
  1277. using namespace AssetBuilderSDK;
  1278. m_mockBuilderResponder.AddBuilder(
  1279. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1280. "fingerprint1");
  1281. m_mockBuilderResponder.AddBuilder(
  1282. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1283. "fingerprint2");
  1284. m_assetProcessorManager->ComputeBuilderDirty();
  1285. // remove one:
  1286. m_mockBuilderResponder.m_assetBuilderDescs.pop_back();
  1287. m_assetProcessorManager->ComputeBuilderDirty();
  1288. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1289. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1290. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 0);
  1291. }
  1292. // if a builder changes its pattern matching, it should be dirty, and also, it should count as add or remove.
  1293. TEST_F(BuilderDirtiness, BuilderDirtiness_ChangedPattern_CountsAsNew)
  1294. {
  1295. using namespace AzToolsFramework::AssetSystem;
  1296. using namespace AssetProcessor;
  1297. using namespace AssetBuilderSDK;
  1298. m_mockBuilderResponder.AddBuilder(
  1299. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1300. "fingerprint1");
  1301. m_mockBuilderResponder.AddBuilder(
  1302. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1303. "fingerprint2");
  1304. m_mockBuilderResponder.AddBuilder(
  1305. "builder3", { AssetBuilderSDK::AssetBuilderPattern("*.bar", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1306. "fingerprint3");
  1307. m_mockBuilderResponder.AddBuilder(
  1308. "builder4", { AssetBuilderSDK::AssetBuilderPattern("*.baz", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1309. "fingerprint4");
  1310. m_assetProcessorManager->ComputeBuilderDirty();
  1311. // here, we change the actual text of the pattern to match
  1312. size_t whichToChange = 1;
  1313. // here, we change the pattern type but not the pattern to match
  1314. AssetBuilderPattern oldPattern = m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns[0];
  1315. oldPattern.m_pattern = "*.somethingElse";
  1316. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.clear();
  1317. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.emplace_back(oldPattern);
  1318. m_assetProcessorManager->ComputeBuilderDirty();
  1319. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1320. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1321. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1322. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_busId));
  1323. m_mockBuilderResponder.BusDisconnect();
  1324. }
  1325. TEST_F(BuilderDirtiness, BuilderDirtiness_ChangedPatternType_CountsAsNew)
  1326. {
  1327. using namespace AzToolsFramework::AssetSystem;
  1328. using namespace AssetProcessor;
  1329. using namespace AssetBuilderSDK;
  1330. m_mockBuilderResponder.AddBuilder(
  1331. "builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1332. "fingerprint1");
  1333. m_mockBuilderResponder.AddBuilder(
  1334. "builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1335. "fingerprint2");
  1336. m_mockBuilderResponder.AddBuilder(
  1337. "builder3", { AssetBuilderSDK::AssetBuilderPattern("*.bar", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1338. "fingerprint3");
  1339. m_mockBuilderResponder.AddBuilder(
  1340. "builder4", { AssetBuilderSDK::AssetBuilderPattern("*.baz", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1,
  1341. "fingerprint4");
  1342. m_assetProcessorManager->ComputeBuilderDirty();
  1343. size_t whichToChange = 2;
  1344. // here, we change the pattern type but not the pattern to match
  1345. AssetBuilderPattern oldPattern = m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns[0];
  1346. oldPattern.m_type = AssetBuilderPattern::Regex;
  1347. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.clear();
  1348. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.emplace_back(oldPattern);
  1349. m_assetProcessorManager->ComputeBuilderDirty();
  1350. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1351. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1352. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1353. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_busId));
  1354. m_mockBuilderResponder.BusDisconnect();
  1355. }
  1356. TEST_F(BuilderDirtiness, BuilderDirtiness_NewPattern_CountsAsNewBuilder)
  1357. {
  1358. using namespace AzToolsFramework::AssetSystem;
  1359. using namespace AssetProcessor;
  1360. using namespace AssetBuilderSDK;
  1361. m_mockBuilderResponder.AddBuilder("builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint1");
  1362. m_mockBuilderResponder.AddBuilder("builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint2");
  1363. m_mockBuilderResponder.AddBuilder("builder3", { AssetBuilderSDK::AssetBuilderPattern("*.bar", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint3");
  1364. m_mockBuilderResponder.AddBuilder("builder4", { AssetBuilderSDK::AssetBuilderPattern("*.baz", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint4");
  1365. m_assetProcessorManager->ComputeBuilderDirty();
  1366. size_t whichToChange = 3;
  1367. // here, we add an additional pattern that wasn't there before:
  1368. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.clear();
  1369. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.buzz", AssetBuilderPattern::Wildcard));
  1370. m_assetProcessorManager->ComputeBuilderDirty();
  1371. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1372. EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
  1373. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1374. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_busId));
  1375. m_mockBuilderResponder.BusDisconnect();
  1376. }
  1377. // changing the "version" of a builder should be equivalent to changing its analysis fingerprint - ie
  1378. // it should not count as adding a new builder.
  1379. TEST_F(BuilderDirtiness, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder)
  1380. {
  1381. using namespace AzToolsFramework::AssetSystem;
  1382. using namespace AssetProcessor;
  1383. using namespace AssetBuilderSDK;
  1384. m_mockBuilderResponder.AddBuilder("builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint1");
  1385. m_mockBuilderResponder.AddBuilder("builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint2");
  1386. m_mockBuilderResponder.AddBuilder("builder3", { AssetBuilderSDK::AssetBuilderPattern("*.bar", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint3");
  1387. m_mockBuilderResponder.AddBuilder("builder4", { AssetBuilderSDK::AssetBuilderPattern("*.baz", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint4");
  1388. m_assetProcessorManager->ComputeBuilderDirty();
  1389. size_t whichToChange = 3;
  1390. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_version++;
  1391. m_assetProcessorManager->ComputeBuilderDirty();
  1392. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1393. EXPECT_FALSE(m_assetProcessorManager->m_buildersAddedOrRemoved); // <-- note, we don't expect this to be considered a 'new builder'
  1394. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1395. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_busId));
  1396. m_mockBuilderResponder.BusDisconnect();
  1397. }
  1398. // changing the "analysis fingerprint" of a builder should not count as an addition or removal
  1399. // but should still result in that specific builder being considered as a dirty builder.
  1400. TEST_F(BuilderDirtiness, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder)
  1401. {
  1402. using namespace AzToolsFramework::AssetSystem;
  1403. using namespace AssetProcessor;
  1404. using namespace AssetBuilderSDK;
  1405. m_mockBuilderResponder.AddBuilder("builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint1");
  1406. m_mockBuilderResponder.AddBuilder("builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint2");
  1407. m_mockBuilderResponder.AddBuilder("builder3", { AssetBuilderSDK::AssetBuilderPattern("*.bar", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint3");
  1408. m_mockBuilderResponder.AddBuilder("builder4", { AssetBuilderSDK::AssetBuilderPattern("*.baz", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint4");
  1409. m_assetProcessorManager->ComputeBuilderDirty();
  1410. size_t whichToChange = 3;
  1411. m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_analysisFingerprint = "changed!!";
  1412. m_assetProcessorManager->ComputeBuilderDirty();
  1413. EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
  1414. EXPECT_FALSE(m_assetProcessorManager->m_buildersAddedOrRemoved); // <-- note, we don't expect this to be considered a 'new builder'
  1415. EXPECT_EQ(m_assetProcessorManager->CountDirtyBuilders(), 1);
  1416. EXPECT_TRUE(m_assetProcessorManager->IsBuilderDirty(m_mockBuilderResponder.m_assetBuilderDescs[whichToChange].m_busId));
  1417. m_mockBuilderResponder.BusDisconnect();
  1418. m_mockApplicationManager->BusConnect();
  1419. }
  1420. // ------------------------------------------------------------------------------------------------
  1421. // QueryAbsolutePathDependenciesRecursive section
  1422. // ------------------------------------------------------------------------------------------------
  1423. TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest)
  1424. {
  1425. using namespace AzToolsFramework::AssetDatabase;
  1426. // A depends on B, which depends on both C and D
  1427. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/a.txt"), QString("tempdata\n"));
  1428. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/b.txt"), QString("tempdata\n"));
  1429. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/c.txt"), QString("tempdata\n"));
  1430. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/d.txt"), QString("tempdata\n"));
  1431. SourceFileDependencyEntry newEntry1; // a depends on B
  1432. newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1433. newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
  1434. newEntry1.m_sourceGuid = m_aUuid;
  1435. newEntry1.m_dependsOnSource = PathOrUuid(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData());
  1436. SourceFileDependencyEntry newEntry2; // b depends on C
  1437. newEntry2.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1438. newEntry2.m_builderGuid = AZ::Uuid::CreateRandom();
  1439. newEntry2.m_sourceGuid = m_bUuid;
  1440. newEntry2.m_dependsOnSource = PathOrUuid(m_cUuid);
  1441. SourceFileDependencyEntry newEntry3; // b also depends on D
  1442. newEntry3.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1443. newEntry3.m_builderGuid = AZ::Uuid::CreateRandom();
  1444. newEntry3.m_sourceGuid = m_bUuid;
  1445. newEntry3.m_dependsOnSource = PathOrUuid("d.txt");
  1446. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry1));
  1447. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry2));
  1448. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry3));
  1449. AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
  1450. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource );
  1451. EXPECT_EQ(dependencies.size(), 4); // a depends on b, c, and d - with the latter two being indirect.
  1452. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1453. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1454. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
  1455. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1456. // make sure the corresponding values in the map are also correct
  1457. EXPECT_STREQ(dependencies[m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()].c_str(), m_aUuid.ToFixedString(false, false).c_str());
  1458. EXPECT_STREQ(dependencies[m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()].c_str(), m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData());
  1459. EXPECT_STREQ(dependencies[m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()].c_str(), m_cUuid.ToFixedString(false, false).c_str());
  1460. EXPECT_STREQ(dependencies[m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()].c_str(), "d.txt");
  1461. dependencies.clear();
  1462. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_bUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1463. EXPECT_EQ(dependencies.size(), 3); // b depends on c, and d
  1464. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1465. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
  1466. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1467. // eliminate b --> c
  1468. ASSERT_TRUE(m_assetProcessorManager->m_stateData->RemoveSourceFileDependency(newEntry2.m_sourceDependencyID));
  1469. m_assetProcessorManager->m_dependencyCache = {};
  1470. dependencies.clear();
  1471. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1472. EXPECT_EQ(dependencies.size(), 3); // a depends on b and d, but no longer c
  1473. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1474. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1475. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1476. }
  1477. TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest)
  1478. {
  1479. // test to make sure that different TYPES of dependencies work as expected.
  1480. using namespace AzToolsFramework::AssetDatabase;
  1481. // Cache does not handle mixed dependency types
  1482. m_assetProcessorManager->m_dependencyCacheEnabled = false;
  1483. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/a.txt"), QString("tempdata\n"));
  1484. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/b.txt"), QString("tempdata\n"));
  1485. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/c.txt"), QString("tempdata\n"));
  1486. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/d.txt"), QString("tempdata\n"));
  1487. SourceFileDependencyEntry newEntry1; // a depends on B as a SOURCE dependency.
  1488. newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1489. newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
  1490. newEntry1.m_sourceGuid = m_aUuid;
  1491. newEntry1.m_dependsOnSource = PathOrUuid(m_bUuid);
  1492. newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
  1493. SourceFileDependencyEntry newEntry2; // b depends on C as a JOB dependency
  1494. newEntry2.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1495. newEntry2.m_builderGuid = AZ::Uuid::CreateRandom();
  1496. newEntry2.m_sourceGuid = m_bUuid;
  1497. newEntry2.m_dependsOnSource = PathOrUuid("c.txt");
  1498. newEntry2.m_typeOfDependency = SourceFileDependencyEntry::DEP_JobToJob;
  1499. SourceFileDependencyEntry newEntry3; // b also depends on D as a SOURCE dependency
  1500. newEntry3.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1501. newEntry3.m_builderGuid = AZ::Uuid::CreateRandom();
  1502. newEntry3.m_sourceGuid = m_bUuid;
  1503. newEntry3.m_dependsOnSource = PathOrUuid("d.txt");
  1504. newEntry3.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource;
  1505. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry1));
  1506. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry2));
  1507. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry3));
  1508. AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
  1509. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1510. // note that a depends on b, c, and d - with the latter two being indirect.
  1511. // however, since b's dependency on C is via JOB, and we're asking for SOURCE only, we should not see C.
  1512. EXPECT_EQ(dependencies.size(), 3);
  1513. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1514. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1515. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1516. dependencies.clear();
  1517. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_bUuid, dependencies, SourceFileDependencyEntry::DEP_JobToJob);
  1518. // b depends on c, and d - but we're asking for job dependencies only, so we should not get anything except C and B
  1519. EXPECT_EQ(dependencies.size(), 2);
  1520. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1521. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
  1522. // now ask for ALL kinds and you should get the full tree.
  1523. dependencies.clear();
  1524. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_Any);
  1525. EXPECT_EQ(dependencies.size(), 4);
  1526. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1527. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1528. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
  1529. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1530. }
  1531. // since we need these files to still produce a 0-based fingerprint, we need them to
  1532. // still do a best guess at absolute path, when they are missing.
  1533. TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders)
  1534. {
  1535. using namespace AzToolsFramework::AssetDatabase;
  1536. // A depends on B, which depends on both C and D
  1537. // Remove b and c files
  1538. AZ::IO::SystemFile::Delete(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData());
  1539. AZ::IO::SystemFile::Delete(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData());
  1540. AzToolsFramework::AssetDatabase::SourceDatabaseEntry entry;
  1541. m_assetProcessorManager->m_stateData->GetSourceBySourceGuid(m_bUuid, entry);
  1542. m_assetProcessorManager->m_stateData->RemoveSource(entry.m_sourceID);
  1543. m_assetProcessorManager->m_stateData->GetSourceBySourceGuid(m_cUuid, entry);
  1544. m_assetProcessorManager->m_stateData->RemoveSource(entry.m_sourceID);
  1545. SourceFileDependencyEntry newEntry1; // a depends on B
  1546. newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1547. newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
  1548. newEntry1.m_sourceGuid = m_aUuid;
  1549. newEntry1.m_dependsOnSource = PathOrUuid(m_bUuid);
  1550. SourceFileDependencyEntry newEntry2; // b depends on C
  1551. newEntry2.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1552. newEntry2.m_builderGuid = AZ::Uuid::CreateRandom();
  1553. newEntry2.m_sourceGuid = m_bUuid;
  1554. newEntry2.m_dependsOnSource = PathOrUuid("c.txt");
  1555. SourceFileDependencyEntry newEntry3; // b also depends on D
  1556. newEntry3.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1557. newEntry3.m_builderGuid = AZ::Uuid::CreateRandom();
  1558. newEntry3.m_sourceGuid = m_bUuid;
  1559. newEntry3.m_dependsOnSource = PathOrUuid("d.txt");
  1560. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry1));
  1561. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry2));
  1562. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry3));
  1563. AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
  1564. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1565. EXPECT_EQ(dependencies.size(), 2); // b and c don't exist, so only expect a and d
  1566. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1567. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1568. dependencies.clear();
  1569. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_bUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1570. EXPECT_EQ(dependencies.size(), 1); // c doesn't exist, so only expect d
  1571. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1572. // eliminate b --> c
  1573. ASSERT_TRUE(m_assetProcessorManager->m_stateData->RemoveSourceFileDependency(newEntry2.m_sourceDependencyID));
  1574. m_assetProcessorManager->m_dependencyCache = {};
  1575. dependencies.clear();
  1576. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1577. EXPECT_EQ(dependencies.size(), 2); // a depends on b and d, but no longer c
  1578. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1579. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1580. }
  1581. // Test to make sure dependencies on non-asset files are included
  1582. TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_DependenciesOnNonAssetsIncluded)
  1583. {
  1584. using namespace AzToolsFramework::AssetDatabase;
  1585. // A depends on B, which depends on both C and D
  1586. // Delete b and c from the database, making them "non asset" files
  1587. AzToolsFramework::AssetDatabase::SourceDatabaseEntry entry;
  1588. m_assetProcessorManager->m_stateData->GetSourceBySourceGuid(m_bUuid, entry);
  1589. m_assetProcessorManager->m_stateData->RemoveSource(entry.m_sourceID);
  1590. m_assetProcessorManager->m_stateData->GetSourceBySourceGuid(m_cUuid, entry);
  1591. m_assetProcessorManager->m_stateData->RemoveSource(entry.m_sourceID);
  1592. SourceFileDependencyEntry newEntry1; // a depends on B
  1593. newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1594. newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
  1595. newEntry1.m_sourceGuid = m_aUuid;
  1596. newEntry1.m_dependsOnSource = PathOrUuid("b.txt");
  1597. SourceFileDependencyEntry newEntry2; // b depends on C
  1598. newEntry2.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1599. newEntry2.m_builderGuid = AZ::Uuid::CreateRandom();
  1600. newEntry2.m_sourceGuid = m_bUuid;
  1601. newEntry2.m_dependsOnSource = PathOrUuid("c.txt");
  1602. SourceFileDependencyEntry newEntry3; // b also depends on D
  1603. newEntry3.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
  1604. newEntry3.m_builderGuid = AZ::Uuid::CreateRandom();
  1605. newEntry3.m_sourceGuid = m_bUuid;
  1606. newEntry3.m_dependsOnSource = PathOrUuid(m_dUuid);
  1607. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry1));
  1608. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry2));
  1609. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependency(newEntry3));
  1610. AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
  1611. m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive(m_aUuid, dependencies, SourceFileDependencyEntry::DEP_SourceToSource);
  1612. EXPECT_EQ(dependencies.size(), 4);
  1613. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
  1614. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
  1615. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
  1616. EXPECT_NE(dependencies.find(m_assetRootDir.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
  1617. }
  1618. TEST_F(AssetProcessorManagerTest, BuilderSDK_API_CreateJobs_HasValidParameters_WithNoOutputFolder)
  1619. {
  1620. // here we push a file change through APM and make sure that "CreateJobs" has correct parameters, with no output redirection
  1621. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/test_text.txt"));
  1622. UnitTestUtils::CreateDummyFile(absPath);
  1623. m_mockApplicationManager->ResetMockBuilderCreateJobCalls();
  1624. m_isIdling = false;
  1625. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  1626. // wait for AP to become idle.
  1627. ASSERT_TRUE(BlockUntilIdle(5000));
  1628. ASSERT_EQ(m_mockApplicationManager->GetMockBuilderCreateJobCalls(), 1);
  1629. AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> builderTxtBuilder;
  1630. ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", builderTxtBuilder));
  1631. const AssetBuilderSDK::CreateJobsRequest &req = builderTxtBuilder->GetLastCreateJobRequest();
  1632. EXPECT_STREQ(req.m_watchFolder.c_str(), m_assetRootDir.absoluteFilePath("subfolder1").toUtf8().constData());
  1633. EXPECT_STREQ(req.m_sourceFile.c_str(), "test_text.txt"); // only the name should be there, no output prefix.
  1634. EXPECT_NE(req.m_sourceFileUUID, AZ::Uuid::CreateNull());
  1635. EXPECT_TRUE(req.HasPlatform("pc"));
  1636. EXPECT_TRUE(req.HasPlatformWithTag("desktop"));
  1637. }
  1638. TEST_F(AssetProcessorManagerTest, BuilderSDK_API_CreateJobs_HasValidParameters_WithOutputRedirectedFolder)
  1639. {
  1640. // here we push a file change through APM and make sure that "CreateJobs" has correct parameters, with no output redirection
  1641. QString absPath(m_assetRootDir.absoluteFilePath("subfolder2/test_text.txt"));
  1642. UnitTestUtils::CreateDummyFile(absPath);
  1643. m_mockApplicationManager->ResetMockBuilderCreateJobCalls();
  1644. m_isIdling = false;
  1645. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  1646. ASSERT_TRUE(BlockUntilIdle(5000));
  1647. ASSERT_EQ(m_mockApplicationManager->GetMockBuilderCreateJobCalls(), 1);
  1648. AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> builderTxtBuilder;
  1649. ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", builderTxtBuilder));
  1650. const AssetBuilderSDK::CreateJobsRequest &req = builderTxtBuilder->GetLastCreateJobRequest();
  1651. // this test looks identical to the above test, but the important piece of information here is that
  1652. // subfolder2 has its output redirected in the cache
  1653. // this test makes sure that the CreateJobs API is completely unaffected by that and none of the internal database stuff
  1654. // is reflected by the API.
  1655. EXPECT_STREQ(req.m_watchFolder.c_str(), m_assetRootDir.absoluteFilePath("subfolder2").toUtf8().constData());
  1656. EXPECT_STREQ(req.m_sourceFile.c_str(), "test_text.txt"); // only the name should be there, no output prefix.
  1657. EXPECT_NE(req.m_sourceFileUUID, AZ::Uuid::CreateNull());
  1658. EXPECT_TRUE(req.HasPlatform("pc"));
  1659. EXPECT_TRUE(req.HasPlatformWithTag("desktop"));
  1660. }
  1661. void AbsolutePathProductDependencyTest::SetUp()
  1662. {
  1663. using namespace AzToolsFramework::AssetDatabase;
  1664. AssetProcessorManagerTest::SetUp();
  1665. m_scanFolderInfo = m_config->GetScanFolderByPath(m_assetRootDir.absoluteFilePath("subfolder4"));
  1666. ASSERT_TRUE(m_scanFolderInfo != nullptr);
  1667. SourceDatabaseEntry sourceEntry(
  1668. m_scanFolderInfo->ScanFolderID(),
  1669. /*sourceName - arbitrary*/ "a.txt",
  1670. AZ::Uuid::CreateRandom(),
  1671. /*analysisFingerprint - arbitrary*/ "abcdefg");
  1672. m_assetProcessorManager->m_stateData->SetSource(sourceEntry);
  1673. AZ::Uuid mockBuilderUuid("{73AC8C3B-C30E-4C0D-97E4-4C5060C4E821}");
  1674. JobDatabaseEntry jobEntry(
  1675. sourceEntry.m_sourceID,
  1676. /*jobKey - arbitrary*/ "Mock Job",
  1677. /*fingerprint - arbitrary*/ 123456,
  1678. m_testPlatform.c_str(),
  1679. mockBuilderUuid,
  1680. AzToolsFramework::AssetSystem::JobStatus::Completed,
  1681. /*jobRunKey - arbitrary*/ 1);
  1682. m_assetProcessorManager->m_stateData->SetJob(jobEntry);
  1683. m_productToHaveDependency = ProductDatabaseEntry(
  1684. jobEntry.m_jobID,
  1685. /*subID - arbitrary*/ 0,
  1686. /*productName - arbitrary*/ "a.output",
  1687. AZ::Data::AssetType::CreateNull());
  1688. m_assetProcessorManager->m_stateData->SetProduct(m_productToHaveDependency);
  1689. }
  1690. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry AbsolutePathProductDependencyTest::SetAndReadAbsolutePathProductDependencyFromRelativePath(
  1691. const AZStd::string& relativePath)
  1692. {
  1693. using namespace AzToolsFramework::AssetDatabase;
  1694. AZStd::string productAbsolutePath =
  1695. AZStd::string::format("%s/%s", m_scanFolderInfo->ScanPath().toUtf8().data(), relativePath.c_str());
  1696. AssetBuilderSDK::ProductPathDependencySet dependencies;
  1697. dependencies.insert(
  1698. AssetBuilderSDK::ProductPathDependency(productAbsolutePath, AssetBuilderSDK::ProductPathDependencyType::SourceFile));
  1699. m_assetProcessorManager->m_pathDependencyManager->SaveUnresolvedDependenciesToDatabase(dependencies, m_productToHaveDependency, m_testPlatform);
  1700. ProductDependencyDatabaseEntry productDependency;
  1701. auto queryFunc = [&](ProductDependencyDatabaseEntry& productDependencyData)
  1702. {
  1703. productDependency = AZStd::move(productDependencyData);
  1704. return false; // stop iterating after the first one. There should actually only be one entry.
  1705. };
  1706. m_assetProcessorManager->m_stateData->QueryUnresolvedProductDependencies(queryFunc);
  1707. return productDependency;
  1708. }
  1709. AZStd::string AbsolutePathProductDependencyTest::BuildScanFolderRelativePath(const AZStd::string& relativePath) const
  1710. {
  1711. // Scan folders write to the database with the $ character wrapped around the scan folder's ID.
  1712. return AZStd::string::format("$%llu$%s", m_scanFolderInfo->ScanFolderID(), relativePath.c_str());
  1713. }
  1714. TEST_F(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_MatchingFileNotAvailable_DependencyCorrectWithScanFolder)
  1715. {
  1716. using namespace AzToolsFramework::AssetDatabase;
  1717. AZStd::string dependencyRelativePath("some/file/path/filename.txt");
  1718. ProductDependencyDatabaseEntry productDependency(SetAndReadAbsolutePathProductDependencyFromRelativePath(dependencyRelativePath));
  1719. // When an absolute path product dependency is created, if part of that path matches a scan folder,
  1720. // the part that matches is replaced with the scan folder's identifier, such as $1$, instead of the absolute path.
  1721. AZStd::string expectedResult(BuildScanFolderRelativePath(dependencyRelativePath));
  1722. ASSERT_EQ(productDependency.m_unresolvedPath, expectedResult);
  1723. ASSERT_NE(productDependency.m_productDependencyID, InvalidEntryId);
  1724. ASSERT_NE(productDependency.m_productPK, InvalidEntryId);
  1725. ASSERT_TRUE(productDependency.m_dependencySourceGuid.IsNull());
  1726. ASSERT_EQ(productDependency.m_platform, m_testPlatform);
  1727. }
  1728. TEST_F(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_MixedCasePath_BecomesLowerCaseInDatabase)
  1729. {
  1730. using namespace AzToolsFramework::AssetDatabase;
  1731. AZStd::string dependencyRelativePath("Some/Mixed/Case/Path.txt");
  1732. ProductDependencyDatabaseEntry productDependency(SetAndReadAbsolutePathProductDependencyFromRelativePath(dependencyRelativePath));
  1733. AZStd::to_lower(dependencyRelativePath.begin(), dependencyRelativePath.end());
  1734. AZStd::string expectedResult(BuildScanFolderRelativePath(dependencyRelativePath));
  1735. ASSERT_EQ(productDependency.m_unresolvedPath, expectedResult);
  1736. ASSERT_NE(productDependency.m_productDependencyID, InvalidEntryId);
  1737. ASSERT_NE(productDependency.m_productPK, InvalidEntryId);
  1738. ASSERT_TRUE(productDependency.m_dependencySourceGuid.IsNull());
  1739. ASSERT_EQ(productDependency.m_platform, m_testPlatform);
  1740. }
  1741. TEST_F(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves)
  1742. {
  1743. using namespace AzToolsFramework::AssetDatabase;
  1744. AZStd::string dependencyRelativePath("somefile.txt");
  1745. ProductDependencyDatabaseEntry productDependency(SetAndReadAbsolutePathProductDependencyFromRelativePath(dependencyRelativePath));
  1746. AZStd::string expectedResult(BuildScanFolderRelativePath(dependencyRelativePath));
  1747. ASSERT_EQ(productDependency.m_unresolvedPath, expectedResult);
  1748. ASSERT_NE(productDependency.m_productDependencyID, InvalidEntryId);
  1749. ASSERT_NE(productDependency.m_productPK, InvalidEntryId);
  1750. ASSERT_TRUE(productDependency.m_dependencySourceGuid.IsNull());
  1751. ASSERT_EQ(productDependency.m_platform, m_testPlatform);
  1752. AZ::Uuid sourceUUID("{4C7B8FD0-9D09-4DCB-A0BC-AEE85B063331}");
  1753. SourceDatabaseEntry matchingSource(
  1754. m_scanFolderInfo->ScanFolderID(),
  1755. dependencyRelativePath.c_str(),
  1756. sourceUUID,
  1757. /*analysisFingerprint - arbitrary*/ "asdfasdf");
  1758. m_assetProcessorManager->m_stateData->SetSource(matchingSource);
  1759. AZ::Uuid mockBuilderUuid("{D314C2FD-757C-4FFA-BEA2-11D41925398A}");
  1760. JobDatabaseEntry jobEntry(
  1761. matchingSource.m_sourceID,
  1762. /*jobKey - arbitrary*/ "Mock Job",
  1763. /*fingerprint - arbitrary*/ 7654321,
  1764. m_testPlatform.c_str(),
  1765. mockBuilderUuid,
  1766. AzToolsFramework::AssetSystem::JobStatus::Completed,
  1767. /*jobRunKey - arbitrary*/ 2);
  1768. m_assetProcessorManager->m_stateData->SetJob(jobEntry);
  1769. ProductDatabaseEntry matchingProductForDependency(
  1770. jobEntry.m_jobID,
  1771. /*subID - arbitrary*/ 5,
  1772. // The absolute path dependency here is to the source file, so the product's file and path
  1773. // don't matter when resolving the dependency.
  1774. /*productName - arbitrary*/ "b.output",
  1775. AZ::Data::AssetType::CreateNull());
  1776. m_assetProcessorManager->m_stateData->SetProduct(matchingProductForDependency);
  1777. m_assetProcessorManager->m_pathDependencyManager->QueueSourceForDependencyResolution(matchingSource);
  1778. m_assetProcessorManager->m_pathDependencyManager->ProcessQueuedDependencyResolves();
  1779. // The product dependency ID shouldn't change when it goes from unresolved to resolved.
  1780. AZStd::vector<ProductDependencyDatabaseEntry> resolvedProductDependencies;
  1781. auto queryFunc = [&](ProductDependencyDatabaseEntry& productDependencyData)
  1782. {
  1783. resolvedProductDependencies.push_back(productDependencyData);
  1784. return true;
  1785. };
  1786. m_assetProcessorManager->m_stateData->QueryProductDependencyByProductId(
  1787. m_productToHaveDependency.m_productID,
  1788. queryFunc);
  1789. ASSERT_EQ(resolvedProductDependencies.size(), 1);
  1790. // The path for a resolved entry should be empty.
  1791. ASSERT_EQ(resolvedProductDependencies[0].m_unresolvedPath, "");
  1792. // The ID and PK should not change.
  1793. ASSERT_EQ(resolvedProductDependencies[0].m_productDependencyID, productDependency.m_productDependencyID);
  1794. ASSERT_EQ(resolvedProductDependencies[0].m_productPK, productDependency.m_productPK);
  1795. // The UUID should now be valid.
  1796. ASSERT_EQ(resolvedProductDependencies[0].m_dependencySourceGuid, matchingSource.m_sourceGuid);
  1797. ASSERT_EQ(resolvedProductDependencies[0].m_dependencySubID, matchingProductForDependency.m_subID);
  1798. ASSERT_EQ(productDependency.m_platform, m_testPlatform);
  1799. }
  1800. void PathDependencyTest::SetUp()
  1801. {
  1802. AssetProcessorManagerTest::SetUp();
  1803. AssetRecognizer rec;
  1804. rec.m_name = "txt files2";
  1805. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1806. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1807. rec.m_supportsCreateJobs = false;
  1808. m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec);
  1809. m_sharedConnection = m_assetProcessorManager->m_stateData.get();
  1810. ASSERT_TRUE(m_sharedConnection);
  1811. }
  1812. void PathDependencyTest::TearDown()
  1813. {
  1814. ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
  1815. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
  1816. AssetProcessorManagerTest::TearDown();
  1817. }
  1818. void PathDependencyTest::CaptureJobs(AZStd::vector<AssetProcessor::JobDetails>& jobDetailsList, const char* sourceFilePath)
  1819. {
  1820. using namespace AssetProcessor;
  1821. using namespace AssetBuilderSDK;
  1822. QString absPath(m_assetRootDir.absoluteFilePath(sourceFilePath));
  1823. UnitTestUtils::CreateDummyFile(absPath, QString::number(QDateTime::currentMSecsSinceEpoch()));
  1824. // prepare to capture the job details as the APM inspects the file.
  1825. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetailsList](JobDetails jobDetails)
  1826. {
  1827. jobDetailsList.push_back(jobDetails);
  1828. });
  1829. // tell the APM about the file:
  1830. m_isIdling = false;
  1831. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  1832. ASSERT_TRUE(BlockUntilIdle(5000));
  1833. // Some tests intentionally finish with mixed slashes, so only use the corrected path to perform the job comparison.
  1834. AZStd::string absPathCorrectSeparator(absPath.toUtf8().constData());
  1835. AZStd::replace(absPathCorrectSeparator.begin(), absPathCorrectSeparator.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  1836. bool foundJob = false;
  1837. for (const auto& details : jobDetailsList)
  1838. {
  1839. ASSERT_FALSE(details.m_autoFail);
  1840. // we should have gotten at least one request to actually process that job:
  1841. AZStd::string jobPath(details.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData());
  1842. AZStd::replace(jobPath.begin(), jobPath.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  1843. if (jobPath == absPathCorrectSeparator)
  1844. {
  1845. foundJob = true;
  1846. }
  1847. }
  1848. ASSERT_TRUE(foundJob);
  1849. QObject::disconnect(connection);
  1850. }
  1851. bool PathDependencyTest::ProcessAsset(TestAsset& asset, const OutputAssetSet& outputAssets, const AssetBuilderSDK::ProductPathDependencySet& dependencies, const AZStd::string& folderPath, const AZStd::string& extension)
  1852. {
  1853. using namespace AssetProcessor;
  1854. using namespace AssetBuilderSDK;
  1855. AZStd::vector<JobDetails> capturedDetails;
  1856. CaptureJobs(capturedDetails, (folderPath + asset.m_name + extension).c_str());
  1857. // Make sure both counts are the same. Otherwise certain code might not trigger
  1858. EXPECT_EQ(capturedDetails.size(), outputAssets.size()) << "The number of captured jobs does not match the number of provided output assets. This can cause AP to not consider the asset to be completely done.";
  1859. int jobSet = 0;
  1860. int subIdCounter = 1;
  1861. for(const auto& outputSet : outputAssets)
  1862. {
  1863. ProcessJobResponse processJobResponse;
  1864. processJobResponse.m_resultCode = ProcessJobResult_Success;
  1865. for (const char* outputExtension : outputSet)
  1866. {
  1867. if(jobSet >= capturedDetails.size() || capturedDetails[jobSet].m_cachePath.empty())
  1868. {
  1869. return false;
  1870. }
  1871. auto filename = capturedDetails[jobSet].m_relativePath / (asset.m_name + outputExtension);
  1872. AssetUtilities::ProductPath productPath{ filename.Native(), capturedDetails[jobSet].m_jobEntry.m_platformInfo.m_identifier };
  1873. UnitTestUtils::CreateDummyFile(productPath.GetCachePath().c_str(), "this is a test output asset");
  1874. JobProduct jobProduct(productPath.GetRelativePath(), AZ::Uuid::CreateRandom(), subIdCounter);
  1875. jobProduct.m_pathDependencies.insert(dependencies.begin(), dependencies.end());
  1876. processJobResponse.m_outputProducts.push_back(jobProduct);
  1877. asset.m_products.push_back(AZ::Data::AssetId(capturedDetails[jobSet].m_jobEntry.m_sourceFileUUID, subIdCounter));
  1878. subIdCounter++;
  1879. }
  1880. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  1881. m_isIdling = false;
  1882. m_assetProcessorManager->AssetProcessed(capturedDetails[jobSet].m_jobEntry, processJobResponse);
  1883. m_assetProcessorManager->CheckForIdle();
  1884. jobSet++;
  1885. }
  1886. return BlockUntilIdle(5000);
  1887. }
  1888. bool SearchDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer, AZ::Data::AssetId assetId)
  1889. {
  1890. for (const auto& containerEntry : dependencyContainer)
  1891. {
  1892. if (containerEntry.m_dependencySourceGuid == assetId.m_guid && containerEntry.m_dependencySubID == assetId.m_subId)
  1893. {
  1894. return true;
  1895. }
  1896. }
  1897. return false;
  1898. }
  1899. void VerifyDependencies(
  1900. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer,
  1901. AZStd::vector<AZ::Data::AssetId> assetIds,
  1902. AZStd::initializer_list<const char*> unresolvedPaths = {})
  1903. {
  1904. EXPECT_EQ(dependencyContainer.size(), assetIds.size() + unresolvedPaths.size());
  1905. for (const AZ::Data::AssetId& assetId : assetIds)
  1906. {
  1907. bool found = false;
  1908. for (const auto& containerEntry : dependencyContainer)
  1909. {
  1910. if (containerEntry.m_dependencySourceGuid == assetId.m_guid && containerEntry.m_dependencySubID == assetId.m_subId)
  1911. {
  1912. found = true;
  1913. break;
  1914. }
  1915. }
  1916. ASSERT_TRUE(found) << "AssetId " << assetId.ToString<AZStd::string>().c_str() << " was not found";
  1917. }
  1918. for (const char* unresolvedPath : unresolvedPaths)
  1919. {
  1920. bool found = false;
  1921. for (const auto& containerEntry : dependencyContainer)
  1922. {
  1923. if (containerEntry.m_unresolvedPath == unresolvedPath &&
  1924. containerEntry.m_dependencySourceGuid.IsNull() &&
  1925. containerEntry.m_dependencySubID == 0)
  1926. {
  1927. found = true;
  1928. break;
  1929. }
  1930. }
  1931. ASSERT_TRUE(found) << "Unresolved path " << unresolvedPath << " was not found";
  1932. }
  1933. }
  1934. void VerifyDependencies(
  1935. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer,
  1936. AZStd::initializer_list<AZ::Data::AssetId> assetIds,
  1937. AZStd::initializer_list<const char*> unresolvedPaths = {})
  1938. {
  1939. VerifyDependencies(dependencyContainer, AZStd::vector<AZ::Data::AssetId>(assetIds), unresolvedPaths);
  1940. }
  1941. void PathDependencyTest::RunWildcardDependencyTestOnPaths(
  1942. const AZStd::string& wildcardDependency,
  1943. const AZStd::vector<AZStd::string>& expectedMatchingPaths,
  1944. const AZStd::vector<AZStd::string>& expectedNotMatchingPaths)
  1945. {
  1946. using namespace AssetProcessor;
  1947. using namespace AssetBuilderSDK;
  1948. AZStd::vector<AZ::Data::AssetId> expectedMatchingProducts;
  1949. for (const auto& assetName : expectedMatchingPaths)
  1950. {
  1951. TestAsset testAsset(assetName.c_str());
  1952. bool result = ProcessAsset(testAsset, { { ".txt" }, {} });
  1953. ASSERT_TRUE(result) << "Failed to Process Assets";
  1954. expectedMatchingProducts.push_back(testAsset.m_products[0]);
  1955. }
  1956. for (const auto& assetName : expectedNotMatchingPaths)
  1957. {
  1958. TestAsset testAsset(assetName.c_str());
  1959. bool result = ProcessAsset(testAsset, { { ".txt" }, {} });
  1960. ASSERT_TRUE(result) << "Failed to Process Assets";
  1961. }
  1962. TestAsset primaryFile("test_text");
  1963. bool result =
  1964. ProcessAsset(primaryFile, { { ".asset" }, {} }, { { wildcardDependency.c_str(), ProductPathDependencyType::ProductFile } });
  1965. ASSERT_TRUE(result) << "Failed to Process main test asset";
  1966. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  1967. result = m_sharedConnection->GetProductDependencies(dependencyContainer);
  1968. ASSERT_TRUE(result) << "Failed to Get Product Dependencies";
  1969. VerifyDependencies(dependencyContainer, expectedMatchingProducts, { wildcardDependency.c_str() });
  1970. }
  1971. // Wildcard matching does not extend past directory markers.
  1972. // So a dependency on "Root/Path/*.txt" will only match text files in Root/Path, and not subfolders.
  1973. // For example, "Root/Path/Subfolder/file.txt" would not be matched.
  1974. TEST_F(PathDependencyTest, WildcardDependencies_WildcardWithPath_ResolveCorrectly)
  1975. {
  1976. RunWildcardDependencyTestOnPaths(
  1977. "root/path/*.txt",
  1978. // Should match - this file is in the matching subfolder
  1979. /*expectedMatchingPaths*/ { "root/path/file1.txt" },
  1980. /*expectedNotMatchingPaths*/
  1981. { // Should not match - This is in a subfolder, and wildcards don't cross directory markers.
  1982. "root/path/subfolder/file3.txt",
  1983. // Should not match - This is in the root folder, and wildcards don't cross directory markers.
  1984. "root/file4.txt" });
  1985. }
  1986. TEST_F(PathDependencyTest, WildcardDependencies_WildcardWithSecondExtension_MatchesFile)
  1987. {
  1988. RunWildcardDependencyTestOnPaths(
  1989. "root/path/*.txt",
  1990. // Should match - the matching rules with wildcard here will match if there are multiple extensions and one matches.
  1991. /*expectedMatchingPaths*/ { "root/path/file1.txt.ext2" },
  1992. /*expectedNotMatchingPaths*/ {});
  1993. }
  1994. TEST_F(PathDependencyTest, WildcardDependencies_WildcardOnFilenameNoDirectories_MatchesAll)
  1995. {
  1996. RunWildcardDependencyTestOnPaths(
  1997. "*.txt",
  1998. // Should match - If a directory is not included in the search, it matches all files regardless of directory.
  1999. /*expectedMatchingPaths*/ { "root/path/to/file1.txt", "another/folder/path/file2.txt", "file3.txt" },
  2000. /*expectedNotMatchingPaths*/ {});
  2001. }
  2002. TEST_F(DuplicateProcessTest, SameAssetProcessedTwice_DependenciesResolveWithoutError)
  2003. {
  2004. using namespace AssetProcessor;
  2005. using namespace AssetBuilderSDK;
  2006. QString sourceFilePath = "subfolder1/test.txt";
  2007. AZStd::vector<JobDetails> jobDetailsList;
  2008. ProductPathDependencySet dependencies = { {"dep1.txt", ProductPathDependencyType::SourceFile}, {"DEP2.asset2", ProductPathDependencyType::ProductFile}, {"Dep2.asset3", ProductPathDependencyType::ProductFile} };
  2009. QString absPath(m_assetRootDir.absoluteFilePath(sourceFilePath));
  2010. UnitTestUtils::CreateDummyFile(absPath);
  2011. // prepare to capture the job details as the APM inspects the file.
  2012. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetailsList](JobDetails jobDetails)
  2013. {
  2014. jobDetailsList.push_back(jobDetails);
  2015. });
  2016. // tell the APM about the file:
  2017. m_isIdling = false;
  2018. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  2019. ASSERT_TRUE(BlockUntilIdle(5000));
  2020. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  2021. ASSERT_TRUE(BlockUntilIdle(5000));
  2022. for(const auto& job : jobDetailsList)
  2023. {
  2024. ProcessJobResponse processJobResponse;
  2025. processJobResponse.m_resultCode = ProcessJobResult_Success;
  2026. {
  2027. auto filename = "test.asset";
  2028. QString outputAssetPath = (job.m_cachePath / filename).AsPosix().c_str();
  2029. UnitTestUtils::CreateDummyFile(outputAssetPath, "this is a test output asset");
  2030. JobProduct jobProduct(filename);
  2031. jobProduct.m_pathDependencies.insert(dependencies.begin(), dependencies.end());
  2032. processJobResponse.m_outputProducts.push_back(jobProduct);
  2033. }
  2034. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  2035. m_isIdling = false;
  2036. m_assetProcessorManager->AssetProcessed(job.m_jobEntry, processJobResponse);
  2037. }
  2038. ASSERT_TRUE(BlockUntilIdle(5000));
  2039. TestAsset dep1("dep1");
  2040. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2041. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1", ".asset2"} }));
  2042. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2", ".asset3"} }));
  2043. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2044. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2045. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2046. VerifyDependencies(dependencyContainer,
  2047. {
  2048. dep1.m_products[0],
  2049. dep1.m_products[1],
  2050. dep2.m_products[1],
  2051. dep2.m_products[2]
  2052. }
  2053. );
  2054. }
  2055. TEST_F(PathDependencyTest, NoLongerProcessedFile_IsRemoved)
  2056. {
  2057. using namespace AssetProcessor;
  2058. using namespace AssetBuilderSDK;
  2059. m_mockApplicationManager->UnRegisterAllBuilders();
  2060. AssetRecognizer rec;
  2061. rec.m_name = "txt files2";
  2062. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  2063. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  2064. rec.m_supportsCreateJobs = false;
  2065. m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec);
  2066. AzFramework::AssetSystem::AssetNotificationMessage details;
  2067. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetMessage, [&details](AzFramework::AssetSystem::AssetNotificationMessage message)
  2068. {
  2069. details = message;
  2070. });
  2071. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/test1.txt"));
  2072. TestAsset testAsset("test1");
  2073. ASSERT_TRUE(ProcessAsset(testAsset, { {".asset1"} }));
  2074. AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
  2075. m_sharedConnection->GetProductsBySourceName("test1.txt", products);
  2076. ASSERT_EQ(products.size(), 1);
  2077. ASSERT_TRUE(QFile::exists(m_normalizedCacheRootDir.absoluteFilePath("pc/test1.asset1").toUtf8().constData()));
  2078. m_mockApplicationManager->UnRegisterAllBuilders();
  2079. m_isIdling = false;
  2080. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  2081. ASSERT_TRUE(BlockUntilIdle(5000));
  2082. products.clear();
  2083. m_sharedConnection->GetProductsBySourceName("test1.txt", products);
  2084. ASSERT_EQ(products.size(), 0);
  2085. ASSERT_FALSE(QFile::exists(m_normalizedCacheRootDir.absoluteFilePath("pc/automatedtesting/test1.asset1").toUtf8().constData()));
  2086. }
  2087. TEST_F(PathDependencyTest, AssetProcessed_Impl_SelfReferrentialProductDependency_DependencyIsRemoved)
  2088. {
  2089. using namespace AssetProcessor;
  2090. using namespace AssetBuilderSDK;
  2091. TestAsset mainFile("testFileName");
  2092. AZStd::vector<JobDetails> capturedDetails;
  2093. CaptureJobs(capturedDetails, ("subfolder1/" + mainFile.m_name + ".txt").c_str());
  2094. ASSERT_FALSE(capturedDetails.empty());
  2095. JobDetails jobDetails = capturedDetails[0];
  2096. AZ::Uuid outputAssetTypeId = AZ::Uuid::CreateRandom();
  2097. int subId = 1;
  2098. ProcessJobResponse processJobResponse;
  2099. processJobResponse.m_resultCode = ProcessJobResult_Success;
  2100. ASSERT_FALSE(jobDetails.m_cachePath.empty());
  2101. // create a product asset
  2102. auto filename = mainFile.m_name + ".asset";
  2103. QString outputAssetPath = (jobDetails.m_cachePath / filename).AsPosix().c_str();
  2104. UnitTestUtils::CreateDummyFile(outputAssetPath, "this is a test output asset");
  2105. // add the new product asset to its own product dependencies list by assetId
  2106. JobProduct jobProduct(filename, outputAssetTypeId, subId);
  2107. AZ::Data::AssetId productAssetId(jobDetails.m_jobEntry.m_sourceFileUUID, subId);
  2108. jobProduct.m_dependencies.push_back(ProductDependency(productAssetId, 5));
  2109. // add the product asset to its own product dependencies list by path
  2110. jobProduct.m_pathDependencies.emplace(ProductPathDependency(AZStd::string::format("%s%s", mainFile.m_name.c_str(), ".asset"), ProductPathDependencyType::ProductFile));
  2111. processJobResponse.m_outputProducts.push_back(jobProduct);
  2112. mainFile.m_products.push_back(productAssetId);
  2113. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  2114. m_errorAbsorber->Clear();
  2115. m_assetProcessorManager->AssetProcessed(jobDetails.m_jobEntry, processJobResponse);
  2116. ASSERT_TRUE(BlockUntilIdle(5000));
  2117. // Verify we have no entries in the ProductDependencies table
  2118. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2119. m_sharedConnection->GetProductDependencies(dependencyContainer);
  2120. ASSERT_TRUE(dependencyContainer.empty());
  2121. // We are testing 2 different dependencies, so we should get 2 warnings
  2122. ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 2);
  2123. m_errorAbsorber->Clear();
  2124. }
  2125. // This test shows the process of deferring resolution of a path dependency works.
  2126. // 1) Resource A comes in with a relative path to resource B which has not been processed yet
  2127. // 2) Resource B is processed, resolving the path dependency on resource A
  2128. TEST_F(PathDependencyTest, AssetProcessed_Impl_DeferredPathResolution)
  2129. {
  2130. using namespace AssetProcessor;
  2131. using namespace AssetBuilderSDK;
  2132. AZStd::vector<TestAsset> dependencySources = { "dep1", "dep2" };
  2133. // Start with mixed casing.
  2134. ProductPathDependencySet dependencies = { {"Dep1.txt", AssetBuilderSDK::ProductPathDependencyType::SourceFile}, {"DEP2.asset2", AssetBuilderSDK::ProductPathDependencyType::ProductFile}, {"dep2.asset3", AssetBuilderSDK::ProductPathDependencyType::ProductFile} }; // Test depending on a source asset, and on a subset of product assets
  2135. TestAsset mainFile("test_text");
  2136. ASSERT_TRUE(ProcessAsset(mainFile, { { ".asset" }, {} }, dependencies));
  2137. // ---------- Verify that we have unresolved path in ProductDependencies table ----------
  2138. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2139. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2140. ASSERT_EQ(dependencyContainer.size(), dependencies.size());
  2141. // All dependencies are stored lowercase in the database. Make the expected dependencies lowercase here to match that.
  2142. for(auto& dependency : dependencies)
  2143. {
  2144. AZStd::to_lower(dependency.m_dependencyPath.begin(), dependency.m_dependencyPath.end());
  2145. }
  2146. for (const auto& dependency : dependencyContainer)
  2147. {
  2148. AssetBuilderSDK::ProductPathDependency actualDependency(dependency.m_unresolvedPath, (dependency.m_dependencyType == AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile) ? ProductPathDependencyType::SourceFile : ProductPathDependencyType::ProductFile);
  2149. ASSERT_THAT(dependencies, ::testing::Contains(actualDependency));
  2150. // Verify that the unresolved path dependency is null.
  2151. ASSERT_TRUE(dependency.m_dependencySourceGuid.IsNull());
  2152. }
  2153. // -------- Process the dependencies to resolve the path dependencies in the first product -----
  2154. for(TestAsset& dependency : dependencySources)
  2155. {
  2156. ASSERT_TRUE(ProcessAsset(dependency, { { ".asset1", ".asset2" }, { ".asset3" } }, {}));
  2157. }
  2158. // ---------- Verify that path has been found and resolved ----------
  2159. dependencyContainer.clear();
  2160. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2161. VerifyDependencies(dependencyContainer,
  2162. {
  2163. dependencySources[0].m_products[0],
  2164. dependencySources[0].m_products[1],
  2165. dependencySources[0].m_products[2],
  2166. dependencySources[1].m_products[1],
  2167. dependencySources[1].m_products[2],
  2168. }
  2169. );
  2170. }
  2171. // This test shows process of how a path dependency is resolved when it is pointing to an asset that has already been processed
  2172. // 1) Resource A is processed, and has with no relative path dependencies
  2173. // 2) Resource B is processed, has a path dependency on resource A
  2174. // 3) An entry is made in the product dependencies table but does not have anything in the unresolved path field
  2175. TEST_F(PathDependencyTest, AssetProcessed_Impl_DeferredPathResolutionAlreadyResolvable)
  2176. {
  2177. using namespace AssetProcessor;
  2178. using namespace AssetBuilderSDK;
  2179. // create dependees
  2180. TestAsset dep1("dep1");
  2181. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2182. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }));
  2183. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} }));
  2184. // -------- Make main test asset, with dependencies on products we just created -----
  2185. TestAsset primaryFile("test_text");
  2186. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, { {"dep1.txt", ProductPathDependencyType::SourceFile}, {"DEP2.asset2", ProductPathDependencyType::ProductFile}, {"Dep2.asset3", ProductPathDependencyType::ProductFile} }));
  2187. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2188. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2189. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2190. VerifyDependencies(dependencyContainer,
  2191. {
  2192. dep1.m_products[0],
  2193. dep1.m_products[1],
  2194. dep2.m_products[1],
  2195. dep2.m_products[2]
  2196. }
  2197. );
  2198. }
  2199. // In most cases, it's expected that asset references (simple and regular) will be only to product files, not source files.
  2200. // Unfortunately, with some legacy systems, this isn't necessary true. To maximize compatibility, the PathDependencyManager
  2201. // does a sanity check on file extensions when for path product dependencies. If it sees a source image format (bmp, tif, jpg, and other supported formats)
  2202. // it will swap the dependency from a product dependency to a source dependency.
  2203. TEST_F(PathDependencyTest, PathProductDependency_SourceImageFileAsProduct_BecomesSourceDependencyInDB)
  2204. {
  2205. using namespace AzToolsFramework::AssetDatabase;
  2206. AZStd::string sourceImageFileExtension("imagefile.bmp");
  2207. TestAsset primaryFile("some_file");
  2208. ASSERT_TRUE(ProcessAsset(
  2209. primaryFile,
  2210. /*outputAssets*/{ { ".asset" }, {} },
  2211. /*dependencies*/{ {sourceImageFileExtension, AssetBuilderSDK::ProductPathDependencyType::ProductFile} }));
  2212. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2213. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2214. ASSERT_EQ(dependencyContainer.size(), 1);
  2215. ASSERT_STREQ(dependencyContainer[0].m_unresolvedPath.c_str(), sourceImageFileExtension.c_str());
  2216. // Verify the dependency type was swapped from product to source.
  2217. ASSERT_EQ(dependencyContainer[0].m_dependencyType, ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile);
  2218. }
  2219. TEST_F(PathDependencyTest, PathProductDependency_MixedSlashes_BecomesCorrectSeparatorInDB)
  2220. {
  2221. using namespace AzToolsFramework::AssetDatabase;
  2222. AZStd::string dependencyRelativePathMixedSlashes("some\\path/with\\mixed/slashes.txt");
  2223. TestAsset primaryFile("some_file");
  2224. ASSERT_TRUE(ProcessAsset(
  2225. primaryFile,
  2226. /*outputAssets*/ { { ".asset" }, {} },
  2227. /*dependencies*/ { {dependencyRelativePathMixedSlashes, AssetBuilderSDK::ProductPathDependencyType::SourceFile} }));
  2228. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2229. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2230. VerifyDependencies(dependencyContainer,
  2231. {},
  2232. {
  2233. // This string is copy & pasted instead of replacing AZ_WRONG_FILESYSTEM_SEPARATOR with AZ_CORRECT_FILESYSTEM_SEPARATOR
  2234. // to improve readability of this test.
  2235. "some/path/with/mixed/slashes.txt"
  2236. });
  2237. }
  2238. TEST_F(PathDependencyTest, PathProductDependency_DoubleSlashes_BecomesCorrectSeparatorInDB)
  2239. {
  2240. using namespace AzToolsFramework::AssetDatabase;
  2241. AZStd::string dependencyRelativePathMixedSlashes("some\\\\path//with\\double/slashes.txt");
  2242. TestAsset primaryFile("some_file");
  2243. ASSERT_TRUE(ProcessAsset(
  2244. primaryFile,
  2245. /*outputAssets*/{ { ".asset" }, {} },
  2246. /*dependencies*/{ {dependencyRelativePathMixedSlashes, AssetBuilderSDK::ProductPathDependencyType::SourceFile} }));
  2247. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2248. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2249. VerifyDependencies(dependencyContainer,
  2250. {},
  2251. {
  2252. // This string is copy & pasted instead of replacing AZ_WRONG_FILESYSTEM_SEPARATOR with AZ_CORRECT_FILESYSTEM_SEPARATOR
  2253. // to improve readability of this test.
  2254. "some/path/with/double/slashes.txt"
  2255. });
  2256. }
  2257. TEST_F(PathDependencyTest, WildcardDependencies_Existing_ResolveCorrectly)
  2258. {
  2259. using namespace AssetProcessor;
  2260. using namespace AssetBuilderSDK;
  2261. // create dependees
  2262. TestAsset dep1("dep1");
  2263. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2264. TestAsset dep3("dep3");
  2265. TestAsset dep4("1deP1");
  2266. bool result = ProcessAsset(dep1, { {".asset1"}, {".asset2"} });
  2267. ASSERT_TRUE(result) << "Failed to Process Assets";
  2268. result = ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} });
  2269. ASSERT_TRUE(result) << "Failed to Process Assets";
  2270. result = ProcessAsset(dep3, { {".asset1", ".asset2"}, {".asset3"} });
  2271. ASSERT_TRUE(result) << "Failed to Process Assets";
  2272. result = ProcessAsset(dep4, { {".asset1"}, {".asset3"} }); // This product will match on both dependencies, this will check to make sure we don't get duplicates
  2273. ASSERT_TRUE(result) << "Failed to Process Assets";
  2274. // -------- Make main test asset, with dependencies on products we just created -----
  2275. TestAsset primaryFile("test_text");
  2276. result = ProcessAsset(primaryFile, { { ".asset" }, {} }, { {"*p1.txt", ProductPathDependencyType::SourceFile}, {"*.asset3", ProductPathDependencyType::ProductFile} });
  2277. ASSERT_TRUE(result) << "Failed to Process main test asset";
  2278. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2279. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2280. result = m_sharedConnection->GetProductDependencies(dependencyContainer);
  2281. ASSERT_TRUE(result)<< "Failed to Get Product Dependencies";
  2282. VerifyDependencies(dependencyContainer,
  2283. {
  2284. dep1.m_products[0],
  2285. dep1.m_products[1],
  2286. dep2.m_products[2],
  2287. dep3.m_products[2],
  2288. dep4.m_products[0],
  2289. dep4.m_products[1]
  2290. },
  2291. { "*p1.txt", "*.asset3" }
  2292. );
  2293. }
  2294. TEST_F(PathDependencyTest, WildcardDependencies_ExcludePathsExisting_ResolveCorrectly)
  2295. {
  2296. using namespace AssetProcessor;
  2297. using namespace AssetBuilderSDK;
  2298. // create dependees
  2299. TestAsset dep1("dep1");
  2300. TestAsset depdep1("dep/dep1");
  2301. TestAsset depdepdep1("dep/dep/dep1");
  2302. TestAsset dep2("dep2");
  2303. TestAsset depdep2("dep/dep2");
  2304. TestAsset depdepdep2("dep/dep/dep2");
  2305. TestAsset dep3("dep3");
  2306. bool result = ProcessAsset(dep1, { {".asset1"}, {} });
  2307. ASSERT_TRUE(result) << "Failed to Process Assets";
  2308. result = ProcessAsset(depdep1, { {".asset2"}, {} });
  2309. ASSERT_TRUE(result) << "Failed to Process Assets";
  2310. result = ProcessAsset(depdepdep1, { {".asset2"}, {} });
  2311. ASSERT_TRUE(result) << "Failed to Process Assets";
  2312. result = ProcessAsset(dep2, { {".asset3"}, {".asset4"} });
  2313. ASSERT_TRUE(result) << "Failed to Process Assets";
  2314. result = ProcessAsset(depdep2, { {".asset3"}, {} });
  2315. ASSERT_TRUE(result) << "Failed to Process Assets";
  2316. result = ProcessAsset(depdepdep2, { {".asset3"}, {} });
  2317. ASSERT_TRUE(result) << "Failed to Process Assets";
  2318. result = ProcessAsset(dep3, { {".asset4"}, {} });
  2319. ASSERT_TRUE(result) << "Failed to Process Assets";
  2320. // -------- Make two main test assets, with dependencies on products we just created -----
  2321. TestAsset primaryFile1("test_text_1");
  2322. result = ProcessAsset(primaryFile1, { { ".asset" }, {} }, {
  2323. {"*p1.txt", ProductPathDependencyType::SourceFile},
  2324. {"dep3.txt", ProductPathDependencyType::SourceFile},
  2325. {":dep3.txt", ProductPathDependencyType::SourceFile},
  2326. {":dep/dep/*p1.txt", ProductPathDependencyType::SourceFile},
  2327. {":dep/dep1.txt", ProductPathDependencyType::SourceFile},
  2328. {"*.asset3", ProductPathDependencyType::ProductFile},
  2329. {"dep2.asset4", ProductPathDependencyType::ProductFile},
  2330. {":dep/dep/dep2.asset3", ProductPathDependencyType::ProductFile},
  2331. {":dep/dep/dep/dep/*.asset3", ProductPathDependencyType::ProductFile},
  2332. {":dep2.asset4", ProductPathDependencyType::ProductFile}});
  2333. ASSERT_TRUE(result) << "Failed to Process main test asset " << primaryFile1.m_name.c_str();
  2334. TestAsset primaryFile2("test_text_2");
  2335. result = ProcessAsset(primaryFile2, { { ".asset" }, {} }, {
  2336. {"*p1.txt", ProductPathDependencyType::SourceFile},
  2337. {"*.asset3", ProductPathDependencyType::ProductFile} });
  2338. ASSERT_TRUE(result) << "Failed to Process main test asset" << primaryFile2.m_name.c_str();
  2339. AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productContainer;
  2340. result = m_sharedConnection->GetProducts(productContainer);
  2341. ASSERT_TRUE(result) << "Failed to Get Products";
  2342. // ---------- Verify that the dependency was recorded and excluded paths were not resolved ----------
  2343. auto product = AZStd::find_if(productContainer.begin(), productContainer.end(),
  2344. [&primaryFile1](const auto& product)
  2345. {
  2346. return product.m_productName.ends_with(primaryFile1.m_name + ".asset");
  2347. });
  2348. ASSERT_TRUE(product != productContainer.end()) << "Failed to Get Product of " << primaryFile1.m_name.c_str();
  2349. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2350. result = m_sharedConnection->GetProductDependenciesByProductID(product->m_productID, dependencyContainer);
  2351. ASSERT_TRUE(result) << "Failed to Get Product Dependencies";
  2352. VerifyDependencies(dependencyContainer,
  2353. {
  2354. dep1.m_products[0],
  2355. dep2.m_products[0]
  2356. },
  2357. { "*p1.txt", "dep3.txt", ":dep3.txt", ":dep/dep/*p1.txt", ":dep/dep1.txt",
  2358. "*.asset3", "dep2.asset4", ":dep/dep/dep2.asset3", ":dep/dep/dep/dep/*.asset3", ":dep2.asset4" }
  2359. );
  2360. // ---------- Verify that the dependency was recorded and the excluded path dependencies defined for another asset didn't effect the product dependencies of the current one ----------
  2361. product = AZStd::find_if(productContainer.begin(), productContainer.end(),
  2362. [&primaryFile2](const auto& product)
  2363. {
  2364. return product.m_productName.ends_with(primaryFile2.m_name + ".asset");
  2365. });
  2366. ASSERT_TRUE(product != productContainer.end()) << "Failed to Get Product of " << primaryFile2.m_name.c_str();
  2367. dependencyContainer.clear();
  2368. result = m_sharedConnection->GetProductDependenciesByProductID(product->m_productID, dependencyContainer);
  2369. ASSERT_TRUE(result) << "Failed to Get Product Dependencies";
  2370. VerifyDependencies(dependencyContainer,
  2371. {
  2372. dep1.m_products[0],
  2373. depdep1.m_products[0],
  2374. depdepdep1.m_products[0],
  2375. dep2.m_products[0],
  2376. depdep2.m_products[0],
  2377. depdepdep2.m_products[0],
  2378. },
  2379. { "*p1.txt", "*.asset3" }
  2380. );
  2381. // Test asset PrimaryFile1 has 4 conflict dependencies
  2382. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 4);
  2383. m_errorAbsorber->Clear();
  2384. }
  2385. TEST_F(PathDependencyTest, WildcardDependencies_Deferred_ResolveCorrectly)
  2386. {
  2387. using namespace AssetProcessor;
  2388. using namespace AssetBuilderSDK;
  2389. // -------- Make main test asset, with dependencies on products that don't exist yet -----
  2390. TestAsset primaryFile("test_text");
  2391. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, { {"*p1.txt", ProductPathDependencyType::SourceFile}, {"*.asset3", ProductPathDependencyType::ProductFile} }));
  2392. // create dependees
  2393. TestAsset dep1("dep1");
  2394. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2395. TestAsset dep3("dep3");
  2396. TestAsset dep4("1deP1");
  2397. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }));
  2398. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} }));
  2399. ASSERT_TRUE(ProcessAsset(dep3, { {".asset1", ".asset2"}, {".asset3"} }));
  2400. ASSERT_TRUE(ProcessAsset(dep4, { {".asset1"}, {} }));
  2401. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2402. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2403. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2404. VerifyDependencies(dependencyContainer,
  2405. {
  2406. dep1.m_products[0],
  2407. dep1.m_products[1],
  2408. dep2.m_products[2],
  2409. dep3.m_products[2],
  2410. dep4.m_products[0]
  2411. },
  2412. { "*p1.txt", "*.asset3" }
  2413. );
  2414. }
  2415. TEST_F(PathDependencyTest, WildcardDependencies_ExcludedPathDeferred_ResolveCorrectly)
  2416. {
  2417. using namespace AssetProcessor;
  2418. using namespace AssetBuilderSDK;
  2419. // -------- Make two main test assets, with dependencies on products that don't exist yet -----
  2420. TestAsset primaryFile1("test_text_1");
  2421. bool result = ProcessAsset(primaryFile1, { { ".asset" }, {} }, {
  2422. {"*p1.txt", ProductPathDependencyType::SourceFile},
  2423. {"dep3.txt", ProductPathDependencyType::SourceFile},
  2424. {":dep3.txt", ProductPathDependencyType::SourceFile},
  2425. {":dep/dep/*p1.txt", ProductPathDependencyType::SourceFile},
  2426. {":dep/dep1.txt", ProductPathDependencyType::SourceFile},
  2427. {"*.asset3", ProductPathDependencyType::ProductFile},
  2428. {"dep2.asset4", ProductPathDependencyType::ProductFile},
  2429. {":dep/dep/dep2.asset3", ProductPathDependencyType::ProductFile},
  2430. {":dep/dep/dep/dep/*.asset3", ProductPathDependencyType::ProductFile},
  2431. {":dep2.asset4", ProductPathDependencyType::ProductFile}});
  2432. ASSERT_TRUE(result) << "Failed to Process main test asset";
  2433. TestAsset primaryFile2("test_text_2");
  2434. result = ProcessAsset(primaryFile2, { { ".asset" }, {} }, {
  2435. {"*p1.txt", ProductPathDependencyType::SourceFile},
  2436. {"*.asset3", ProductPathDependencyType::ProductFile} });
  2437. ASSERT_TRUE(result) << "Failed to Process main test asset";
  2438. // create dependees
  2439. TestAsset dep1("dep1");
  2440. TestAsset depdep1("dep/dep1");
  2441. TestAsset depdepdep1("dep/dep/dep1");
  2442. TestAsset dep2("dep2");
  2443. TestAsset depdep2("dep/dep2");
  2444. TestAsset depdepdep2("dep/dep/dep2");
  2445. TestAsset dep3("dep3");
  2446. result = ProcessAsset(dep1, { {".asset1"}, {} });
  2447. ASSERT_TRUE(result) << "Failed to Process Assets";
  2448. result = ProcessAsset(depdep1, { {".asset2"}, {} });
  2449. ASSERT_TRUE(result) << "Failed to Process Assets";
  2450. result = ProcessAsset(depdepdep1, { {".asset2"}, {} });
  2451. ASSERT_TRUE(result) << "Failed to Process Assets";
  2452. result = ProcessAsset(dep2, { {".asset3"}, {".asset4"} });
  2453. ASSERT_TRUE(result) << "Failed to Process Assets";
  2454. result = ProcessAsset(depdep2, { {".asset3"}, {} });
  2455. ASSERT_TRUE(result) << "Failed to Process Assets";
  2456. result = ProcessAsset(depdepdep2, { {".asset3"}, {} });
  2457. ASSERT_TRUE(result) << "Failed to Process Assets";
  2458. result = ProcessAsset(dep3, { {".asset4"}, {} });
  2459. ASSERT_TRUE(result) << "Failed to Process Assets";
  2460. AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productContainer;
  2461. result = m_sharedConnection->GetProducts(productContainer);
  2462. ASSERT_TRUE(result) << "Failed to Get Products";
  2463. // ---------- Verify that the dependency was recorded and exlcuded paths were not resolved ----------
  2464. auto product = AZStd::find_if(productContainer.begin(), productContainer.end(),
  2465. [&primaryFile1](const auto& product)
  2466. {
  2467. return product.m_productName.ends_with(primaryFile1.m_name + ".asset");
  2468. });
  2469. ASSERT_TRUE(product != productContainer.end()) << "Failed to Get Product of " << primaryFile1.m_name.c_str();
  2470. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2471. result = m_sharedConnection->GetProductDependenciesByProductID(product->m_productID, dependencyContainer);
  2472. ASSERT_TRUE(result) << "Failed to Get Product Dependencies";
  2473. VerifyDependencies(dependencyContainer,
  2474. {
  2475. dep1.m_products[0],
  2476. dep2.m_products[0]
  2477. },
  2478. { "*p1.txt", "dep3.txt", ":dep3.txt", ":dep/dep/*p1.txt", ":dep/dep1.txt",
  2479. "*.asset3", "dep2.asset4", ":dep/dep/dep2.asset3", ":dep/dep/dep/dep/*.asset3", ":dep2.asset4" }
  2480. );
  2481. // ---------- Verify that the dependency was recorded and the excluded path dependencies defined for another asset didn't effect the product dependencies of the current one ----------
  2482. product = AZStd::find_if(productContainer.begin(), productContainer.end(),
  2483. [&primaryFile2](const auto& product)
  2484. {
  2485. return product.m_productName.ends_with(primaryFile2.m_name + ".asset");
  2486. });
  2487. ASSERT_TRUE(product != productContainer.end()) << "Failed to Get Product of " << primaryFile2.m_name.c_str();
  2488. dependencyContainer.clear();
  2489. result = m_sharedConnection->GetProductDependenciesByProductID(product->m_productID, dependencyContainer);
  2490. ASSERT_TRUE(result) << "Failed to Get Product Dependencies";
  2491. VerifyDependencies(dependencyContainer,
  2492. {
  2493. dep1.m_products[0],
  2494. depdep1.m_products[0],
  2495. depdepdep1.m_products[0],
  2496. dep2.m_products[0],
  2497. depdep2.m_products[0],
  2498. depdepdep2.m_products[0],
  2499. },
  2500. { "*p1.txt", "*.asset3" }
  2501. );
  2502. // Test asset PrimaryFile1 has 4 conflict dependencies
  2503. // After test assets dep2 and dep3 are processed,
  2504. // another 2 errors will be raised because of the confliction
  2505. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 6);
  2506. m_errorAbsorber->Clear();
  2507. }
  2508. void PathDependencyTest::RunWildcardTest(bool useCorrectDatabaseSeparator, AssetBuilderSDK::ProductPathDependencyType pathDependencyType, bool buildDependenciesFirst)
  2509. {
  2510. using namespace AssetProcessor;
  2511. using namespace AssetBuilderSDK;
  2512. // create dependees
  2513. // Wildcard resolution of paths with back slashes is not supported on non-windows platforms, so we need to construct those test cases differently
  2514. TestAsset matchingDepWithForwardSlash("testFolder/someFileName");
  2515. AZStd::string depWithPlatformCompatibleSlash;
  2516. AzFramework::StringFunc::Path::Join("testFolder", "anotherFileName", depWithPlatformCompatibleSlash);
  2517. TestAsset matchingDepWithPlatformCompatibleSlash(depWithPlatformCompatibleSlash.c_str());
  2518. AZStd::string depWithMixedSlashes;
  2519. AzFramework::StringFunc::Path::Join("someRootFolder/testFolder", "anotherFileName", depWithMixedSlashes, true, false);
  2520. TestAsset matchingDepDeeperFolderMixedSlashes(depWithMixedSlashes.c_str());
  2521. TestAsset notMatchingDepInSubfolder("unmatchedFolder/arbitraryFileName");
  2522. if (buildDependenciesFirst)
  2523. {
  2524. ASSERT_TRUE(ProcessAsset(matchingDepWithForwardSlash, { {".asset"}, {} })) << "Failed to Process " << matchingDepWithForwardSlash.m_name.c_str();
  2525. ASSERT_TRUE(ProcessAsset(matchingDepWithPlatformCompatibleSlash, { {".asset"}, {} })) << "Failed to Process " << matchingDepWithPlatformCompatibleSlash.m_name.c_str();
  2526. ASSERT_TRUE(ProcessAsset(matchingDepDeeperFolderMixedSlashes, { {".asset"}, {} })) << "Failed to Process " << matchingDepDeeperFolderMixedSlashes.m_name.c_str();
  2527. ASSERT_TRUE(ProcessAsset(notMatchingDepInSubfolder, { {".asset"}, {} })) << "Failed to Process " << notMatchingDepInSubfolder.m_name.c_str();
  2528. }
  2529. // -------- Make main test asset, with dependencies on products we just created -----
  2530. TestAsset primaryFile("test_text");
  2531. const char* databaseSeparator = useCorrectDatabaseSeparator ? AZ_CORRECT_DATABASE_SEPARATOR_STRING : AZ_WRONG_DATABASE_SEPARATOR_STRING;
  2532. AZStd::string extension = (pathDependencyType == ProductPathDependencyType::SourceFile) ? "txt" : "asset";
  2533. AZStd::string wildcardString = AZStd::string::format("*testFolder%s*.%s", databaseSeparator, extension.c_str());
  2534. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, { {wildcardString.c_str(), pathDependencyType}, })) << "Failed to Process " << primaryFile.m_name.c_str();
  2535. if (!buildDependenciesFirst)
  2536. {
  2537. ASSERT_TRUE(ProcessAsset(matchingDepWithForwardSlash, { {".asset"}, {} })) << "Failed to Process " << matchingDepWithForwardSlash.m_name.c_str();
  2538. ASSERT_TRUE(ProcessAsset(matchingDepWithPlatformCompatibleSlash, { {".asset"}, {} })) << "Failed to Process " << matchingDepWithPlatformCompatibleSlash.m_name.c_str();
  2539. ASSERT_TRUE(ProcessAsset(matchingDepDeeperFolderMixedSlashes, { {".asset"}, {} })) << "Failed to Process " << matchingDepDeeperFolderMixedSlashes.m_name.c_str();
  2540. ASSERT_TRUE(ProcessAsset(notMatchingDepInSubfolder, { {".asset"}, {} })) << "Failed to Process " << notMatchingDepInSubfolder.m_name.c_str();
  2541. }
  2542. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2543. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2544. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2545. // Dependencies are always written to the database in lower case with the correct separator.
  2546. AZStd::to_lower(wildcardString.begin(), wildcardString.end());
  2547. AZStd::replace(wildcardString.begin(), wildcardString.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  2548. VerifyDependencies(dependencyContainer,
  2549. {
  2550. matchingDepWithForwardSlash.m_products[0],
  2551. matchingDepWithPlatformCompatibleSlash.m_products[0],
  2552. matchingDepDeeperFolderMixedSlashes.m_products[0]
  2553. },
  2554. // Paths become lowercase in the DB
  2555. { wildcardString.c_str() }
  2556. );
  2557. }
  2558. TEST_F(PathDependencyTest, WildcardSourcePathDependenciesWithForwardSlash_Existing_ResolveCorrectly)
  2559. {
  2560. RunWildcardTest(
  2561. /*useCorrectDatabaseSeparator*/ true,
  2562. AssetBuilderSDK::ProductPathDependencyType::SourceFile,
  2563. /*buildDependenciesFirst*/ true);
  2564. }
  2565. TEST_F(PathDependencyTest, WildcardSourcePathDependenciesWithBackSlash_Existing_ResolveCorrectly)
  2566. {
  2567. RunWildcardTest(
  2568. /*useCorrectDatabaseSeparator*/ false,
  2569. AssetBuilderSDK::ProductPathDependencyType::SourceFile,
  2570. /*buildDependenciesFirst*/ true);
  2571. }
  2572. TEST_F(PathDependencyTest, WildcardSourcePathDependenciesWithForwardSlash_Deferred_ResolveCorrectly)
  2573. {
  2574. RunWildcardTest(
  2575. /*useCorrectDatabaseSeparator*/ true,
  2576. AssetBuilderSDK::ProductPathDependencyType::SourceFile,
  2577. /*buildDependenciesFirst*/ false);
  2578. }
  2579. TEST_F(PathDependencyTest, WildcardSourcePathDependenciesWithBackSlash_Deferred_ResolveCorrectly)
  2580. {
  2581. RunWildcardTest(
  2582. /*useCorrectDatabaseSeparator*/ false,
  2583. AssetBuilderSDK::ProductPathDependencyType::SourceFile,
  2584. /*buildDependenciesFirst*/ false);
  2585. }
  2586. TEST_F(PathDependencyTest, WildcardProductPathDependenciesWithForwardSlash_Existing_ResolveCorrectly)
  2587. {
  2588. RunWildcardTest(
  2589. /*useCorrectDatabaseSeparator*/ true,
  2590. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2591. /*buildDependenciesFirst*/ true);
  2592. }
  2593. TEST_F(PathDependencyTest, WildcardProductPathDependenciesWithBackSlash_Existing_ResolveCorrectly)
  2594. {
  2595. RunWildcardTest(
  2596. /*useCorrectDatabaseSeparator*/ false,
  2597. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2598. /*buildDependenciesFirst*/ true);
  2599. }
  2600. TEST_F(PathDependencyTest, WildcardProductPathDependenciesWithForwardSlash_Deferred_ResolveCorrectly)
  2601. {
  2602. RunWildcardTest(
  2603. /*useCorrectDatabaseSeparator*/ true,
  2604. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2605. /*buildDependenciesFirst*/ false);
  2606. }
  2607. TEST_F(PathDependencyTest, WildcardProductPathDependenciesWithBackSlash_Deferred_ResolveCorrectly)
  2608. {
  2609. RunWildcardTest(
  2610. /*useCorrectDatabaseSeparator*/ false,
  2611. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2612. /*buildDependenciesFirst*/ false);
  2613. }
  2614. TEST_F(PathDependencyTest, Wildcard_ResolvingTwice_DependenciesNotDuplicated)
  2615. {
  2616. // Regression test: make sure resolving the dependencies twice doesn't result in duplicate entries in the database
  2617. RunWildcardTest(
  2618. /*useCorrectDatabaseSeparator*/ true,
  2619. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2620. /*buildDependenciesFirst*/ true
  2621. );
  2622. RunWildcardTest(
  2623. /*useCorrectDatabaseSeparator*/ true,
  2624. AssetBuilderSDK::ProductPathDependencyType::ProductFile,
  2625. /*buildDependenciesFirst*/ false);
  2626. }
  2627. // Tests product path dependencies using absolute paths to source files
  2628. TEST_F(PathDependencyTest, AbsoluteDependencies_Existing_ResolveCorrectly)
  2629. {
  2630. using namespace AssetProcessor;
  2631. using namespace AssetBuilderSDK;
  2632. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/dep1.txt"));
  2633. // create dependees
  2634. TestAsset dep1("dep1");
  2635. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }));
  2636. // -------- Make main test asset, with dependencies on products we just created -----
  2637. TestAsset primaryFile("test_text");
  2638. ASSERT_TRUE(ProcessAsset(primaryFile, { {".asset"} , {} }, { {absPath.toUtf8().constData(), ProductPathDependencyType::SourceFile} }));
  2639. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2640. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2641. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2642. VerifyDependencies(dependencyContainer,
  2643. {
  2644. dep1.m_products[0],
  2645. dep1.m_products[1]
  2646. }
  2647. );
  2648. }
  2649. // Tests product path dependencies using absolute paths to source files
  2650. TEST_F(PathDependencyTest, AbsoluteDependencies_Deferred_ResolveCorrectly)
  2651. {
  2652. using namespace AssetProcessor;
  2653. using namespace AssetBuilderSDK;
  2654. AZStd::string relativePathDep1("dep1.txt");
  2655. QString absPathDep1(m_assetRootDir.absoluteFilePath(QString("subfolder4%1%2").arg(QDir::separator()).arg(relativePathDep1.c_str())));
  2656. auto scanfolder4 = m_config->GetScanFolderForFile(absPathDep1);
  2657. // When an absolute path matches a scan folder, the portion of the path matching that scan folder
  2658. // is replaced with the scan folder's ID.
  2659. AZStd::string absPathDep1WithScanfolder(AZStd::string::format("$%" PRId64 "$%s", aznumeric_cast<int64_t>(scanfolder4->ScanFolderID()), relativePathDep1.c_str()));
  2660. QString absPathDep2(m_assetRootDir.absoluteFilePath("subfolder2/redirected/dep2.txt"));
  2661. QString absPathDep3(m_assetRootDir.absoluteFilePath("subfolder1/dep3.txt"));
  2662. // -------- Make main test asset, with dependencies on products that don't exist yet -----
  2663. TestAsset primaryFile("test_text");
  2664. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} },
  2665. {
  2666. {absPathDep1.toUtf8().constData(), ProductPathDependencyType::SourceFile},
  2667. {absPathDep2.toUtf8().constData(), ProductPathDependencyType::SourceFile},
  2668. {absPathDep3.toUtf8().constData(), ProductPathDependencyType::SourceFile},
  2669. }
  2670. ));
  2671. // create dependees
  2672. TestAsset dep1("dep1");
  2673. TestAsset dep2("dep2");
  2674. TestAsset dep3("dep3");
  2675. // Different scanfolder, same relative file name. This should *not* trigger the dependency. We can't test with another asset in the proper scanfolder because AssetIds are based on relative file name,
  2676. // which means both assets have the same AssetId and there would be no way to tell which one matched
  2677. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }, {}, "subfolder1/"));
  2678. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1"}, {".asset2"} }, {}, "subfolder2/redirected/"));
  2679. ASSERT_TRUE(ProcessAsset(dep3, { {".asset1"}, {".asset2"} }, {}, "subfolder1/")); // test a normal dependency with no prefix
  2680. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2681. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2682. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2683. VerifyDependencies(dependencyContainer,
  2684. {
  2685. dep2.m_products[0],
  2686. dep2.m_products[1],
  2687. dep3.m_products[0],
  2688. dep3.m_products[1],
  2689. }, { absPathDep1WithScanfolder.c_str() }
  2690. );
  2691. }
  2692. TEST_F(PathDependencyTest, ChangeDependencies_Existing_ResolveCorrectly)
  2693. {
  2694. using namespace AssetProcessor;
  2695. using namespace AssetBuilderSDK;
  2696. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/dep1.txt"));
  2697. // create dependees
  2698. TestAsset dep1("dep1");
  2699. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }));
  2700. // -------- Make main test asset, with dependencies on products we just created -----
  2701. TestAsset primaryFile("test_text");
  2702. ASSERT_TRUE(ProcessAsset(primaryFile, { {".asset"} , {} }, { {"dep1.*", ProductPathDependencyType::SourceFile} }));
  2703. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2704. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2705. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2706. VerifyDependencies(dependencyContainer,
  2707. {
  2708. dep1.m_products[0],
  2709. dep1.m_products[1]
  2710. },
  2711. { "dep1.*" }
  2712. );
  2713. // Update again with different dependencies
  2714. ASSERT_TRUE(ProcessAsset(primaryFile, { {".asset"} , {} }, { {absPath.toUtf8().constData(), ProductPathDependencyType::SourceFile} }));
  2715. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2716. dependencyContainer.clear();
  2717. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2718. VerifyDependencies(dependencyContainer,
  2719. {
  2720. dep1.m_products[0],
  2721. dep1.m_products[1]
  2722. }
  2723. );
  2724. }
  2725. TEST_F(PathDependencyTest, MixedPathDependencies_Existing_ResolveCorrectly)
  2726. {
  2727. using namespace AssetProcessor;
  2728. using namespace AssetBuilderSDK;
  2729. // create dependees
  2730. TestAsset dep1("dep1");
  2731. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2732. TestAsset dep3("dep3");
  2733. TestAsset dep4("dep4");
  2734. TestAsset dep5("dep5");
  2735. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/folderA/folderB/dep5.txt"));
  2736. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }, {}, "subfolder1/folderA/folderB/"));
  2737. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2738. ASSERT_TRUE(ProcessAsset(dep3, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2739. ASSERT_TRUE(ProcessAsset(dep4, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2740. ASSERT_TRUE(ProcessAsset(dep5, { {".asset1"}, {} }, {}, "subfolder1/folderA/folderB/"));
  2741. // -------- Make main test asset, with dependencies on products we just created -----
  2742. TestAsset primaryFile("test_text");
  2743. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, {
  2744. {"folderA/folderB\\*1.txt", ProductPathDependencyType::SourceFile}, // wildcard source
  2745. {"folderA/folderB\\*2.asset3", ProductPathDependencyType::ProductFile}, // wildcard product
  2746. {"folderA/folderB\\dep3.txt", ProductPathDependencyType::SourceFile}, // relative source
  2747. {"folderA/folderB\\dep4.asset3", ProductPathDependencyType::ProductFile}, // relative product
  2748. {absPath.toUtf8().constData(), ProductPathDependencyType::SourceFile}, // absolute source
  2749. }));
  2750. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2751. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2752. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2753. VerifyDependencies(dependencyContainer,
  2754. {
  2755. dep1.m_products[0],
  2756. dep1.m_products[1],
  2757. dep2.m_products[2],
  2758. dep3.m_products[0],
  2759. dep3.m_products[1],
  2760. dep3.m_products[2],
  2761. dep4.m_products[2],
  2762. dep5.m_products[0],
  2763. }, { "foldera/folderb/*1.txt", "foldera/folderb/*2.asset3" } // wildcard dependencies always leave an unresolved entry
  2764. );
  2765. }
  2766. TEST_F(PathDependencyTest, MixedPathDependencies_Deferred_ResolveCorrectly)
  2767. {
  2768. using namespace AssetProcessor;
  2769. using namespace AssetBuilderSDK;
  2770. // create dependees
  2771. TestAsset dep1("dep1");
  2772. TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
  2773. TestAsset dep3("dep3");
  2774. TestAsset dep4("dep4");
  2775. TestAsset dep5("dep5");
  2776. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/folderA\\folderB/dep5.txt"));
  2777. // -------- Make main test asset, with dependencies on products that don't exist yet -----
  2778. TestAsset primaryFile("test_text");
  2779. ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, {
  2780. {"folderA/folderB\\*1.txt", ProductPathDependencyType::SourceFile}, // wildcard source
  2781. {"folderA/folderB\\*2.asset3", ProductPathDependencyType::ProductFile}, // wildcard product
  2782. {"folderA/folderB\\dep3.txt", ProductPathDependencyType::SourceFile}, // relative source
  2783. {"folderA/folderB\\dep4.asset3", ProductPathDependencyType::ProductFile}, // relative product
  2784. {absPath.toUtf8().constData(), ProductPathDependencyType::SourceFile}, // absolute source
  2785. }));
  2786. // create dependees
  2787. ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }, {}, "subfolder1/folderA/folderB/"));
  2788. ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2789. ASSERT_TRUE(ProcessAsset(dep3, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2790. ASSERT_TRUE(ProcessAsset(dep4, { {".asset1", ".asset2"}, {".asset3"} }, {}, "subfolder1/folderA/folderB/"));
  2791. ASSERT_TRUE(ProcessAsset(dep5, { {".asset1"}, {} }, {}, "subfolder1/folderA/folderB/"));
  2792. // ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
  2793. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2794. ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
  2795. VerifyDependencies(dependencyContainer,
  2796. {
  2797. dep1.m_products[0],
  2798. dep1.m_products[1],
  2799. dep2.m_products[2],
  2800. dep3.m_products[0],
  2801. dep3.m_products[1],
  2802. dep3.m_products[2],
  2803. dep4.m_products[2],
  2804. dep5.m_products[0],
  2805. }, { "foldera/folderb/*1.txt", "foldera/folderb/*2.asset3" } // wildcard dependencies always leave an unresolved entry
  2806. );
  2807. }
  2808. void MultiplatformPathDependencyTest::SetUp()
  2809. {
  2810. AssetProcessorManagerTest::SetUp();
  2811. m_config = nullptr; // Make sure to clear this out first so the existing config can cleanup before we allocate the new one
  2812. m_config.reset(new AssetProcessor::PlatformConfiguration());
  2813. m_config->EnablePlatform({ "pc", { "host", "renderer", "desktop" } }, true);
  2814. m_config->EnablePlatform({ "provo",{ "console" } }, true);
  2815. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder1"), "subfolder1", "subfolder1", false, true, m_config->GetEnabledPlatforms()));
  2816. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder2"), "subfolder2", "subfolder2", false, true, m_config->GetEnabledPlatforms()));
  2817. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("subfolder3"), "subfolder3", "subfolder3", false, true, m_config->GetEnabledPlatforms()));
  2818. m_config->AddIntermediateScanFolder();
  2819. m_assetProcessorManager = nullptr; // we need to destroy the previous instance before creating a new one
  2820. m_assetProcessorManager = AZStd::make_unique<AssetProcessorManager_Test>(m_config.get());
  2821. m_isIdling = false;
  2822. m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState)
  2823. {
  2824. m_isIdling = newState;
  2825. });
  2826. // Get rid of all the other builders, and add a builder that will process for both platforms
  2827. m_mockApplicationManager->UnRegisterAllBuilders();
  2828. AssetRecognizer rec;
  2829. rec.m_name = "multiplatform txt files";
  2830. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  2831. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  2832. rec.m_platformSpecs.insert({"provo", AssetInternalSpec::Copy});
  2833. rec.m_supportsCreateJobs = false;
  2834. m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec);
  2835. AssetRecognizer rec2;
  2836. rec2.m_name = "single platform ini files";
  2837. rec2.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.ini", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  2838. rec2.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  2839. rec2.m_supportsCreateJobs = false;
  2840. m_mockApplicationManager->RegisterAssetRecognizerAsBuilder(rec2);
  2841. }
  2842. TEST_F(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies)
  2843. {
  2844. // One product will be pc, one will be console (order is non-deterministic)
  2845. TestAsset asset1("testAsset1");
  2846. ASSERT_TRUE(ProcessAsset(asset1, { { ".asset1" },{ ".asset1b" } }, {}));
  2847. // Create a new asset that will only get processed by one platform, make it depend on both products of testAsset1
  2848. TestAsset asset2("asset2");
  2849. ASSERT_TRUE(ProcessAsset(asset2, { { ".asset1" } }, { { "testAsset1.asset1", AssetBuilderSDK::ProductPathDependencyType::ProductFile },{ "testAsset1.asset1b", AssetBuilderSDK::ProductPathDependencyType::ProductFile } }, "subfolder1/", ".ini"));
  2850. AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
  2851. ASSERT_TRUE(sharedConnection);
  2852. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2853. // Since asset2 was only made for one platform only one of its dependencies should be resolved.
  2854. sharedConnection->GetProductDependencies(dependencyContainer);
  2855. int resolvedCount = 0;
  2856. int unresolvedCount = 0;
  2857. for (const auto& dep : dependencyContainer)
  2858. {
  2859. if (dep.m_unresolvedPath.empty())
  2860. {
  2861. resolvedCount++;
  2862. }
  2863. else
  2864. {
  2865. unresolvedCount++;
  2866. }
  2867. }
  2868. ASSERT_EQ(resolvedCount, 1);
  2869. ASSERT_EQ(unresolvedCount, 1);
  2870. ASSERT_NE(SearchDependencies(dependencyContainer, asset1.m_products[0]), SearchDependencies(dependencyContainer, asset1.m_products[1]));
  2871. }
  2872. TEST_F(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution)
  2873. {
  2874. // Create a new asset that will only get processed by one platform, make it depend on both products of testAsset1
  2875. TestAsset asset2("asset2");
  2876. ASSERT_TRUE(ProcessAsset(asset2, { { ".asset1" } }, { { "testAsset1.asset1", AssetBuilderSDK::ProductPathDependencyType::ProductFile },{ "testAsset1.asset1b", AssetBuilderSDK::ProductPathDependencyType::ProductFile } }, "subfolder1/", ".ini"));
  2877. // One product will be pc, one will be console (order is non-deterministic)
  2878. TestAsset asset1("testAsset1");
  2879. ASSERT_TRUE(ProcessAsset(asset1, { { ".asset1" },{ ".asset1b" } }, {}));
  2880. AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
  2881. ASSERT_TRUE(sharedConnection);
  2882. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2883. // Since asset2 was only made for one platform only one of its dependencies should be resolved.
  2884. sharedConnection->GetProductDependencies(dependencyContainer);
  2885. int resolvedCount = 0;
  2886. int unresolvedCount = 0;
  2887. for (const auto& dep : dependencyContainer)
  2888. {
  2889. if (dep.m_unresolvedPath.empty())
  2890. {
  2891. resolvedCount++;
  2892. }
  2893. else
  2894. {
  2895. unresolvedCount++;
  2896. }
  2897. }
  2898. ASSERT_EQ(resolvedCount, 1);
  2899. ASSERT_EQ(unresolvedCount, 1);
  2900. ASSERT_NE(SearchDependencies(dependencyContainer, asset1.m_products[0]), SearchDependencies(dependencyContainer, asset1.m_products[1]));
  2901. }
  2902. TEST_F(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms)
  2903. {
  2904. TestAsset asset2("asset2");
  2905. bool result = ProcessAsset(
  2906. asset2, { { ".output" }, { ".output" } }, { { "*1.output", AssetBuilderSDK::ProductPathDependencyType::ProductFile } }, "subfolder1/",
  2907. ".txt");
  2908. ASSERT_TRUE(result);
  2909. TestAsset asset1("asset1");
  2910. result = ProcessAsset(asset1, { { ".output" }, { ".output" } }, {});
  2911. ASSERT_TRUE(result);
  2912. AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
  2913. ASSERT_TRUE(sharedConnection);
  2914. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2915. sharedConnection->GetProductDependencies(dependencyContainer);
  2916. int resolvedCount = 0;
  2917. int unresolvedCount = 0;
  2918. for (const auto& dep : dependencyContainer)
  2919. {
  2920. if (dep.m_unresolvedPath.empty())
  2921. {
  2922. resolvedCount++;
  2923. }
  2924. else
  2925. {
  2926. unresolvedCount++;
  2927. }
  2928. }
  2929. ASSERT_EQ(resolvedCount, 2);
  2930. ASSERT_EQ(unresolvedCount, 2);
  2931. VerifyDependencies(dependencyContainer, { asset1.m_products[0], asset1.m_products[1] }, { "*1.output", "*1.output" });
  2932. }
  2933. TEST_F(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath)
  2934. {
  2935. // One product will be pc, one will be console (order is non-deterministic)
  2936. TestAsset asset1("testAsset1");
  2937. ASSERT_TRUE(ProcessAsset(asset1, { { ".asset1" },{ ".asset1b" } }, {}));
  2938. // Create a new asset that will only get processed by one platform, make it depend on both products of testAsset1
  2939. TestAsset asset2("asset2");
  2940. ASSERT_TRUE(ProcessAsset(asset2, { { ".asset1" } }, { { "testAsset1.txt", AssetBuilderSDK::ProductPathDependencyType::SourceFile } }, "subfolder1/", ".ini"));
  2941. AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
  2942. ASSERT_TRUE(sharedConnection);
  2943. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
  2944. // Since asset2 was only made for one platform only one of its dependencies should be resolved.
  2945. sharedConnection->GetProductDependencies(dependencyContainer);
  2946. int resolvedCount = 0;
  2947. int unresolvedCount = 0;
  2948. for (const auto& dep : dependencyContainer)
  2949. {
  2950. if (dep.m_unresolvedPath.empty())
  2951. {
  2952. resolvedCount++;
  2953. }
  2954. else
  2955. {
  2956. unresolvedCount++;
  2957. }
  2958. }
  2959. ASSERT_EQ(resolvedCount, 1);
  2960. ASSERT_EQ(unresolvedCount, 0);
  2961. ASSERT_NE(SearchDependencies(dependencyContainer, asset1.m_products[0]), SearchDependencies(dependencyContainer, asset1.m_products[1]));
  2962. }
  2963. // this tests exists to make sure a bug does not regress.
  2964. // when the bug was active, dependencies would be stored in the database incorrectly when different products emitted different dependencies.
  2965. // specifically, any dependency emitted by any product of a given source would show up as a dependency of ALL products for that source.
  2966. TEST_F(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase)
  2967. {
  2968. using namespace AssetProcessor;
  2969. using namespace AssetBuilderSDK;
  2970. /// --------------------- SETUP PHASE - make an asset exist in the database -------------------
  2971. // Create the source file.
  2972. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/test_text.txt"));
  2973. UnitTestUtils::CreateDummyFile(absPath);
  2974. // prepare to capture the job details as the APM inspects the file.
  2975. JobDetails capturedDetails;
  2976. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&capturedDetails](JobDetails jobDetails)
  2977. {
  2978. capturedDetails = jobDetails;
  2979. });
  2980. // tell the APM about the file:
  2981. m_isIdling = false;
  2982. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  2983. ASSERT_TRUE(BlockUntilIdle(5000));
  2984. ASSERT_FALSE(capturedDetails.m_autoFail);
  2985. QObject::disconnect(connection);
  2986. // we should have gotten at least one request to actually process that job:
  2987. ASSERT_STREQ(capturedDetails.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData(), absPath.toUtf8().constData());
  2988. // now simulate the job being done and actually returning a full job finished details which includes dependencies:
  2989. ProcessJobResponse response;
  2990. response.m_resultCode = ProcessJobResult_Success;
  2991. QString destTestPath1 = (capturedDetails.m_cachePath / "test1.txt").AsPosix().c_str();
  2992. QString destTestPath2 = (capturedDetails.m_cachePath / "test2.txt").AsPosix().c_str();
  2993. UnitTestUtils::CreateDummyFile(destTestPath1, "this is the first output");
  2994. UnitTestUtils::CreateDummyFile(destTestPath2, "this is the second output");
  2995. JobProduct productA("test1.txt", AZ::Uuid::CreateRandom(), 1);
  2996. JobProduct productB("test2.txt", AZ::Uuid::CreateRandom(), 2);
  2997. AZ::Data::AssetId expectedIdOfProductA(capturedDetails.m_jobEntry.m_sourceFileUUID, productA.m_productSubID);
  2998. AZ::Data::AssetId expectedIdOfProductB(capturedDetails.m_jobEntry.m_sourceFileUUID, productB.m_productSubID);
  2999. productA.m_dependencies.push_back(ProductDependency(expectedIdOfProductB, 5));
  3000. productB.m_dependencies.push_back(ProductDependency(expectedIdOfProductA, 6));
  3001. response.m_outputProducts.push_back(productA);
  3002. response.m_outputProducts.push_back(productB);
  3003. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  3004. m_isIdling = false;
  3005. m_assetProcessorManager->AssetProcessed(capturedDetails.m_jobEntry, response);
  3006. ASSERT_TRUE(BlockUntilIdle(5000));
  3007. // note that there exists different tests (in the AssetStateDatabase tests) to directly test the actual database store/get for this
  3008. // the purpose of this test is to just make sure that the Asset Processor Manager actually understood the job dependencies
  3009. // and correctly stored the results into the dependency table.
  3010. //-------------------------------- EVALUATION PHASE -------------------------
  3011. // at this point, the AP will have filed the asset away in its database and we can now validate that it actually
  3012. // did it correctly.
  3013. // We expect to see two dependencies in the dependency table, each with the correct dependency, no duplicates, no lost data.
  3014. AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
  3015. AZStd::unordered_map<AZ::Data::AssetId, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry> capturedTableEntries;
  3016. ASSERT_TRUE(sharedConnection);
  3017. AZStd::size_t countFound = 0;
  3018. bool queryresult = sharedConnection->QueryProductDependenciesTable(
  3019. [&capturedTableEntries, &countFound](AZ::Data::AssetId& asset, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
  3020. {
  3021. ++countFound;
  3022. capturedTableEntries[asset] = entry;
  3023. return true;
  3024. });
  3025. ASSERT_TRUE(queryresult);
  3026. // this also asserts uniqueness.
  3027. ASSERT_EQ(countFound, 2);
  3028. ASSERT_EQ(capturedTableEntries.size(), countFound); // if they were not unique asset IDs, they would have collapsed on top of each other.
  3029. // make sure both assetIds are present:
  3030. ASSERT_NE(capturedTableEntries.find(expectedIdOfProductA), capturedTableEntries.end());
  3031. ASSERT_NE(capturedTableEntries.find(expectedIdOfProductB), capturedTableEntries.end());
  3032. // make sure both refer to the other and nothing else.
  3033. EXPECT_EQ(capturedTableEntries[expectedIdOfProductA].m_dependencySourceGuid, expectedIdOfProductB.m_guid);
  3034. EXPECT_EQ(capturedTableEntries[expectedIdOfProductA].m_dependencySubID, expectedIdOfProductB.m_subId);
  3035. EXPECT_EQ(capturedTableEntries[expectedIdOfProductA].m_dependencyFlags, 5);
  3036. EXPECT_EQ(capturedTableEntries[expectedIdOfProductB].m_dependencySourceGuid, expectedIdOfProductA.m_guid);
  3037. EXPECT_EQ(capturedTableEntries[expectedIdOfProductB].m_dependencySubID, expectedIdOfProductA.m_subId);
  3038. EXPECT_EQ(capturedTableEntries[expectedIdOfProductB].m_dependencyFlags, 6);
  3039. }
  3040. // this test exists to make sure a bug does not regress.
  3041. // when the bug was active, source files with multiple products would cause the asset processor to repeatedly process them
  3042. // due to a timing problem. Specifically, if the products were not successfully moved to the output directory quickly enough
  3043. // it would assume something was wrong, and re-trigger the job, which cancelled the already-in-flight job currently busy copying
  3044. // the product files to the cache to finalize it.
  3045. TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
  3046. {
  3047. using namespace AssetProcessor;
  3048. using namespace AssetBuilderSDK;
  3049. // constants to adjust - if this regresses you can turn it up much higher for a stress test.
  3050. const int numOutputsToSimulate = 50;
  3051. // --------------------- SETUP PHASE - make an asset exist in the database as if the job is complete -------------------
  3052. // The asset needs multiple job products.
  3053. // Create the source file.
  3054. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/test_text.txt"));
  3055. UnitTestUtils::CreateDummyFile(absPath);
  3056. // prepare to capture the job details as the APM inspects the file.
  3057. JobDetails capturedDetails;
  3058. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&capturedDetails](JobDetails jobDetails)
  3059. {
  3060. capturedDetails = jobDetails;
  3061. });
  3062. // tell the APM about the file:
  3063. m_isIdling = false;
  3064. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  3065. ASSERT_TRUE(BlockUntilIdle(5000));
  3066. QObject::disconnect(connection);
  3067. // we should have gotten at least one request to actually process that job:
  3068. ASSERT_STREQ(capturedDetails.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData(), absPath.toUtf8().constData());
  3069. // now simulate the job being done and actually returning a full job finished details which includes dependencies:
  3070. ProcessJobResponse response;
  3071. response.m_resultCode = ProcessJobResult_Success;
  3072. for (int outputIdx = 0; outputIdx < numOutputsToSimulate; ++outputIdx)
  3073. {
  3074. auto fileNameToGenerate = AZStd::string::format("test%d.txt", outputIdx);
  3075. QString filePathToGenerate = (capturedDetails.m_cachePath / fileNameToGenerate).AsPosix().c_str();
  3076. UnitTestUtils::CreateDummyFile(filePathToGenerate, "an output");
  3077. JobProduct product(fileNameToGenerate, AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(outputIdx));
  3078. response.m_outputProducts.push_back(product);
  3079. }
  3080. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  3081. m_isIdling = false;
  3082. m_assetProcessorManager->AssetProcessed(capturedDetails.m_jobEntry, response);
  3083. ASSERT_TRUE(BlockUntilIdle(5000));
  3084. // at this point, everything should be up to date and ready for the test - there should be one source in the database
  3085. // with numOutputsToSimulate products.
  3086. // now, we simulate a job running to process the asset again, by modifying the timestamp on the file to be at least one second later.
  3087. // this is because on some operating systems (such as mac) the resolution of file time stamps is at least one second.
  3088. #ifdef AZ_PLATFORM_WINDOWS
  3089. int milliseconds = 10;
  3090. #else
  3091. int milliseconds = 1001;
  3092. #endif
  3093. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(milliseconds));
  3094. UnitTestUtils::CreateDummyFile(absPath, "Completely different file data");
  3095. // with the source file changed, tell it to process it again:
  3096. // prepare to capture the job details as the APM inspects the file.
  3097. connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&capturedDetails](JobDetails jobDetails)
  3098. {
  3099. capturedDetails = jobDetails;
  3100. });
  3101. // tell the APM about the file:
  3102. m_isIdling = false;
  3103. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
  3104. ASSERT_TRUE(BlockUntilIdle(5000));
  3105. QObject::disconnect(connection);
  3106. // we should have gotten at least one request to actually process that job:
  3107. ASSERT_STREQ(capturedDetails.m_jobEntry.GetAbsoluteSourcePath().toUtf8().constData(), absPath.toUtf8().constData());
  3108. ASSERT_FALSE(capturedDetails.m_autoFail);
  3109. ASSERT_FALSE(capturedDetails.m_cachePath.empty());
  3110. // ----------------------------- TEST BEGINS HERE -----------------------------
  3111. // simulte a very slow computer processing the file one output at a time and feeding file change notifies:
  3112. // FROM THIS POINT ON we should see no new job create / cancellation or anything since we're just going to be messing with the cache.
  3113. bool gotUnexpectedAssetToProcess = false;
  3114. connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&gotUnexpectedAssetToProcess](JobDetails /*jobDetails*/)
  3115. {
  3116. gotUnexpectedAssetToProcess = true;
  3117. });
  3118. // this function tells APM about a file and waits for it to idle, if waitForIdle is true.
  3119. // basically, it simulates the file watcher firing on events from the cache since file watcher events
  3120. // come in on the queue at any time a file changes, sourced from a different thread.
  3121. auto notifyAPM = [this, &gotUnexpectedAssetToProcess](const char* functionToCall, QString filePath, bool waitForIdle)
  3122. {
  3123. if (waitForIdle)
  3124. {
  3125. m_isIdling = false;
  3126. }
  3127. QMetaObject::invokeMethod(m_assetProcessorManager.get(), functionToCall, Qt::QueuedConnection, Q_ARG(QString, QString(filePath)));
  3128. if (waitForIdle)
  3129. {
  3130. ASSERT_TRUE(BlockUntilIdle(5000));
  3131. }
  3132. ASSERT_FALSE(gotUnexpectedAssetToProcess);
  3133. };
  3134. response = AssetBuilderSDK::ProcessJobResponse();
  3135. response.m_resultCode = ProcessJobResult_Success;
  3136. for (int outputIdx = 0; outputIdx < numOutputsToSimulate; ++outputIdx)
  3137. {
  3138. // every second one, we dont wait at all and let it rapidly process, to preturb the timing.
  3139. bool shouldBlockAndWaitThisTime = outputIdx % 2 == 0;
  3140. auto fileNameToGenerate = AZStd::string::format("test%d.txt", outputIdx);
  3141. QString filePathToGenerate = (capturedDetails.m_cachePath / fileNameToGenerate).AsPosix().c_str();
  3142. JobProduct product(fileNameToGenerate, AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(outputIdx));
  3143. response.m_outputProducts.push_back(product);
  3144. AssetProcessor::ProcessingJobInfoBus::Broadcast(&AssetProcessor::ProcessingJobInfoBus::Events::BeginCacheFileUpdate, filePathToGenerate.toUtf8().data());
  3145. AZ::IO::SystemFile::Delete(filePathToGenerate.toUtf8().constData());
  3146. // simulate the file watcher showing the deletion occuring:
  3147. notifyAPM("AssessDeletedFile", filePathToGenerate, shouldBlockAndWaitThisTime);
  3148. UnitTestUtils::CreateDummyFile(filePathToGenerate, "an output");
  3149. // let the APM go for a significant amount of time so that it simulates a slow thread copying a large file with lots of events about it pouring in.
  3150. for (int repeatLoop = 0; repeatLoop < 100; ++repeatLoop)
  3151. {
  3152. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(filePathToGenerate)));
  3153. QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 1);
  3154. ASSERT_FALSE(gotUnexpectedAssetToProcess);
  3155. }
  3156. // also toss it a "cache modified" call to make sure that this does not spawn further jobs
  3157. // note that assessing modified files in the cache should not result in it spawning jobs or even becoming unidle since it
  3158. // actually ignores modified files in the cache.
  3159. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, QString(filePathToGenerate)));
  3160. QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 1);
  3161. ASSERT_FALSE(gotUnexpectedAssetToProcess);
  3162. // now tell it to stop ignoring the cache delete and let it do the next one.
  3163. AssetProcessor::ProcessingJobInfoBus::Broadcast(
  3164. &AssetProcessor::ProcessingJobInfoBus::Events::EndCacheFileUpdate, filePathToGenerate.toUtf8().data(), false);
  3165. // simulate a "late" deletion notify coming from the file monitor that it outside the "ignore delete" section. This should STILL not generate additional
  3166. // deletion notifies as it should ignore these if the file in fact actually there when it gets around to checking it
  3167. notifyAPM("AssessDeletedFile", filePathToGenerate, shouldBlockAndWaitThisTime);
  3168. }
  3169. // tell the APM that the asset has been processed and allow it to bubble through its event queue:
  3170. m_isIdling = false;
  3171. m_assetProcessorManager->AssetProcessed(capturedDetails.m_jobEntry, response);
  3172. ASSERT_TRUE(BlockUntilIdle(5000));
  3173. ASSERT_FALSE(gotUnexpectedAssetToProcess);
  3174. QObject::disconnect(connection);
  3175. }
  3176. void SourceFileDependenciesTest::SetUp()
  3177. {
  3178. AssetProcessorManagerTest::SetUp();
  3179. m_sourceFileUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"))).GetValueOr(AZ::Uuid());
  3180. ASSERT_FALSE(m_sourceFileUuid.IsNull());
  3181. // The files need to be created first before a UUID can be assigned
  3182. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/a.txt")));
  3183. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/b.txt")));
  3184. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/c.txt")));
  3185. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/d.txt")));
  3186. m_uuidOfA = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/a.txt"))).GetValueOr(AZ::Uuid());
  3187. m_uuidOfB = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/b.txt"))).GetValueOr(AZ::Uuid());
  3188. m_uuidOfC = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/c.txt"))).GetValueOr(AZ::Uuid());
  3189. m_uuidOfD = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder1/d.txt"))).GetValueOr(AZ::Uuid());
  3190. // Clean up the files, different tests have different requirements for which files should exist
  3191. QFile(m_assetRootDir.absoluteFilePath("subfolder1/a.txt")).remove();
  3192. QFile(m_assetRootDir.absoluteFilePath("subfolder1/b.txt")).remove();
  3193. QFile(m_assetRootDir.absoluteFilePath("subfolder1/c.txt")).remove();
  3194. QFile(m_assetRootDir.absoluteFilePath("subfolder1/d.txt")).remove();
  3195. // Cache does not handle mixed dependency types
  3196. m_assetProcessorManager->m_dependencyCacheEnabled = false;
  3197. }
  3198. void SourceFileDependenciesTest::SetupData(
  3199. const AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies,
  3200. const AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
  3201. bool createFile1Dummies,
  3202. bool createFile2Dummies,
  3203. bool primeMap,
  3204. AssetProcessor::AssetProcessorManager::JobToProcessEntry& job)
  3205. {
  3206. // make sure that if we publish some dependencies, they appear:
  3207. m_dummyBuilderUuid = AZ::Uuid::CreateRandom();
  3208. QString relFileName("assetProcessorManagerTest.txt");
  3209. m_absPath = m_assetRootDir.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt");
  3210. m_watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  3211. m_scanFolder = m_config->GetScanFolderByPath(m_watchFolderPath);
  3212. ASSERT_NE(m_scanFolder, nullptr);
  3213. // the above file (assetProcessorManagerTest.txt) will depend on these four files:
  3214. m_dependsOnFile1_Source = m_assetRootDir.absoluteFilePath("subfolder1/a.txt");
  3215. m_dependsOnFile2_Source = m_assetRootDir.absoluteFilePath("subfolder1/b.txt");
  3216. m_dependsOnFile1_Job = m_assetRootDir.absoluteFilePath("subfolder1/c.txt");
  3217. m_dependsOnFile2_Job = m_assetRootDir.absoluteFilePath("subfolder1/d.txt");
  3218. if (createFile1Dummies)
  3219. {
  3220. CreateSourceAndFile("subfolder1/a.txt");
  3221. CreateSourceAndFile("subfolder1/c.txt");
  3222. }
  3223. if (createFile2Dummies)
  3224. {
  3225. CreateSourceAndFile("subfolder1/b.txt");
  3226. CreateSourceAndFile("subfolder1/d.txt");
  3227. }
  3228. // construct the dummy job to feed to the database updater function:
  3229. job.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_absPath);
  3230. job.m_sourceFileInfo.m_scanFolder = m_scanFolder;
  3231. job.m_sourceFileInfo.m_uuid = AssetUtilities::GetSourceUuid(job.m_sourceFileInfo.m_sourceAssetReference).GetValueOr(AZ::Uuid());
  3232. if (primeMap)
  3233. {
  3234. m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[job.m_sourceFileInfo.m_uuid] = job.m_sourceFileInfo.m_sourceAssetReference;
  3235. }
  3236. for (const auto& sourceFileDependency : sourceFileDependencies)
  3237. {
  3238. job.m_sourceFileDependencies.emplace_back(m_dummyBuilderUuid, sourceFileDependency);
  3239. }
  3240. // it is currently assumed that the only fields that we care about in JobDetails is the builder busId and the job dependencies
  3241. // themselves:
  3242. JobDetails newDetails;
  3243. newDetails.m_assetBuilderDesc.m_busId = m_dummyBuilderUuid;
  3244. for (const auto& jobDependency : jobDependencies)
  3245. {
  3246. newDetails.m_jobDependencyList.push_back(jobDependency);
  3247. }
  3248. job.m_jobsToAnalyze.push_back(newDetails);
  3249. // this is the one line that this unit test is really testing:
  3250. m_assetProcessorManager->UpdateSourceFileDependenciesDatabase(job);
  3251. }
  3252. void SourceFileDependenciesTest::PopulateDatabase()
  3253. {
  3254. using namespace AzToolsFramework::AssetDatabase;
  3255. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder(
  3256. m_assetRootDir.absoluteFilePath("subfolder1").toUtf8().constData(), "temp path", "temp path");
  3257. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetScanFolder(scanFolder));
  3258. CreateSourceAndFile("subFolder1/assetProcessorManagerTest.txt");
  3259. }
  3260. AssetBuilderSDK::SourceFileDependency SourceFileDependenciesTest::MakeSourceDependency(const char* file, bool wildcard)
  3261. {
  3262. return { file, AZ::Uuid::CreateNull(),
  3263. wildcard ? AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards
  3264. : AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute };
  3265. }
  3266. AssetBuilderSDK::SourceFileDependency SourceFileDependenciesTest::MakeSourceDependency(AZ::Uuid uuid)
  3267. {
  3268. return { "", uuid };
  3269. }
  3270. AssetBuilderSDK::JobDependency SourceFileDependenciesTest::MakeJobDependency(const char* file)
  3271. {
  3272. return AssetBuilderSDK::JobDependency("pc build", "pc", AssetBuilderSDK::JobDependencyType::Order, MakeSourceDependency(file));
  3273. }
  3274. AssetBuilderSDK::JobDependency SourceFileDependenciesTest::MakeJobDependency(AZ::Uuid uuid)
  3275. {
  3276. return AssetBuilderSDK::JobDependency("pc build", "pc", AssetBuilderSDK::JobDependencyType::Order, MakeSourceDependency(uuid));
  3277. }
  3278. auto SourceFileDependenciesTest::GetDependencyList()
  3279. {
  3280. AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer deps;
  3281. this->m_assetProcessorManager->m_stateData->GetSourceFileDependenciesByBuilderGUIDAndSource(
  3282. m_dummyBuilderUuid, m_sourceFileUuid,
  3283. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency::DEP_Any, deps);
  3284. AZStd::vector<AZStd::string> list;
  3285. for (auto&& entry : deps)
  3286. {
  3287. list.push_back(entry.m_dependsOnSource.ToString());
  3288. }
  3289. return list;
  3290. }
  3291. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_BasicTest)
  3292. {
  3293. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3294. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, true, true, true, job);
  3295. // the rest of this test now performs a series of queries to verify the database was correctly set.
  3296. // this indirectly verifies the QueryAbsolutePathDependenciesRecursive function also but it has its own dedicated tests, above.
  3297. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3298. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3299. // the above function includes the actual source, as an absolute path.
  3300. EXPECT_EQ(deps.size(), 3);
  3301. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3302. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3303. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end());
  3304. deps.clear();
  3305. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3306. // the above function includes the actual source, as an absolute path.
  3307. EXPECT_EQ(deps.size(), 3);
  3308. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3309. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3310. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end());
  3311. deps.clear();
  3312. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3313. // the above function includes the actual source, as an absolute path.
  3314. EXPECT_EQ(deps.size(), 5);
  3315. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3316. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3317. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end());
  3318. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3319. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end());
  3320. }
  3321. TEST_F(SourceFileDependenciesTest, DependenciesSavedWithPathAndUuid_FromAssetIdIsSetCorrectly)
  3322. {
  3323. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3324. SetupData(
  3325. { MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) },
  3326. { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) },
  3327. true,
  3328. true,
  3329. true,
  3330. job);
  3331. AZStd::vector<AzToolsFramework::AssetDatabase::SourceFileDependencyEntry> dependencyEntry;
  3332. m_assetProcessorManager->m_stateData->GetSourceFileDependenciesByDependsOnSource(
  3333. m_uuidOfA,
  3334. "a.txt",
  3335. "a.txt",
  3336. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency::DEP_Any,
  3337. dependencyEntry);
  3338. m_assetProcessorManager->m_stateData->GetSourceFileDependenciesByDependsOnSource(
  3339. m_uuidOfB,
  3340. "b.txt",
  3341. "b.txt",
  3342. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency::DEP_Any,
  3343. dependencyEntry);
  3344. m_assetProcessorManager->m_stateData->GetSourceFileDependenciesByDependsOnSource(
  3345. m_uuidOfC,
  3346. "c.txt",
  3347. "c.txt",
  3348. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency::DEP_Any,
  3349. dependencyEntry);
  3350. m_assetProcessorManager->m_stateData->GetSourceFileDependenciesByDependsOnSource(
  3351. m_uuidOfD,
  3352. "d.txt",
  3353. "d.txt",
  3354. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::TypeOfDependency::DEP_Any,
  3355. dependencyEntry);
  3356. ASSERT_EQ(dependencyEntry.size(), 4);
  3357. // These should be in the order queried above. A and C are path based, so FromAssetId should be false, B and D are UUID based so FromAssetId should be true
  3358. EXPECT_FALSE(dependencyEntry[0].m_fromAssetId);
  3359. EXPECT_TRUE(dependencyEntry[1].m_fromAssetId);
  3360. EXPECT_FALSE(dependencyEntry[2].m_fromAssetId);
  3361. EXPECT_TRUE(dependencyEntry[3].m_fromAssetId);
  3362. }
  3363. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_UpdateTest)
  3364. {
  3365. // make sure that if we remove dependencies that are published, they disappear.
  3366. // so the first part of this test is to put some data in there, the same as before:
  3367. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3368. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, true, true, true, job);
  3369. // in this test, though, we delete some after pushing them in there, and update it again:
  3370. job.m_sourceFileDependencies.pop_back(); // erase the 'b' dependency.
  3371. job.m_jobsToAnalyze[0].m_jobDependencyList.pop_back(); // erase the 'd' dependency, which is by guid.
  3372. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job);
  3373. // now make sure that the same queries omit b and d:
  3374. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3375. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3376. // the above function includes the actual source, as an absolute path.
  3377. EXPECT_EQ(deps.size(), 2);
  3378. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3379. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3380. deps.clear();
  3381. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3382. // the above function includes the actual source, as an absolute path.
  3383. EXPECT_EQ(deps.size(), 2);
  3384. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3385. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3386. deps.clear();
  3387. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3388. // the above function includes the actual source, as an absolute path.
  3389. EXPECT_EQ(deps.size(), 3);
  3390. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3391. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3392. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3393. }
  3394. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid)
  3395. {
  3396. // make sure that if we publish some dependencies, they do not appear if they are missing
  3397. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3398. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, false, true, true, job);
  3399. // the rest of this test now performs a series of queries to verify the database was correctly set.
  3400. // this indirectly verifies the QueryAbsolutePathDependenciesRecursive function also but it has its own dedicated tests, above.
  3401. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3402. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3403. // we should find all of the deps, but not the placeholders.
  3404. EXPECT_EQ(deps.size(), 2);
  3405. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3406. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end()); // b
  3407. deps.clear();
  3408. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3409. // the above function includes the actual source, as an absolute path.
  3410. EXPECT_EQ(deps.size(), 2);
  3411. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3412. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end()); // d
  3413. deps.clear();
  3414. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3415. // the above function includes the actual source, as an absolute path.
  3416. EXPECT_EQ(deps.size(), 3);
  3417. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3418. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end()); // b
  3419. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end()); // d
  3420. }
  3421. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName)
  3422. {
  3423. // make sure that if we publish some dependencies, they do not appear if missing
  3424. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3425. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, true, false, false, job);
  3426. // the rest of this test now performs a series of queries to verify the database was correctly set.
  3427. // this indirectly verifies the QueryAbsolutePathDependenciesRecursive function also but it has its own dedicated tests, above.
  3428. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3429. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3430. // we should find all of the deps, but a and c are missing and thus should not appear.
  3431. EXPECT_EQ(deps.size(), 2);
  3432. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3433. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end()); // a
  3434. deps.clear();
  3435. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3436. EXPECT_EQ(deps.size(), 2);
  3437. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3438. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end()); // c
  3439. deps.clear();
  3440. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3441. EXPECT_EQ(deps.size(), 3);
  3442. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3443. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end()); // a
  3444. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end()); // c
  3445. }
  3446. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear)
  3447. {
  3448. // this test makes sure that when files DO appear that were previously placeholders, the database is updated
  3449. // so the strategy here is to have files b, and d missing, which are declared as dependencies by UUID.
  3450. // then, we make them re-appear later, and check that the database has updated them appropriately.
  3451. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3452. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, true, false, false, job);
  3453. // so at this point, the database should be in the same state as after the UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid test
  3454. // which was already verified, by that test.
  3455. // now that the database has placeholders, we expect them to resolve themselves when we provide the actual files:
  3456. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile2_Source, QString("tempdata\n")));
  3457. // now that B exists, we pretend a job came in to process B. (it doesn't require dependencies to be declared)
  3458. // note that we have to "prime" the map with the UUIDs to the source info for this to work:
  3459. m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfB] = SourceAssetReference(m_watchFolderPath, "b.txt");
  3460. AssetProcessorManager::JobToProcessEntry job2;
  3461. job2.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "b.txt");
  3462. job2.m_sourceFileInfo.m_scanFolder = m_scanFolder;
  3463. job2.m_sourceFileInfo.m_uuid = m_uuidOfB;
  3464. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job2);
  3465. // b should no longer be a placeholder, so both A and B should be present as their actual path.
  3466. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3467. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3468. EXPECT_EQ(deps.size(), 3);
  3469. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3470. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end()); // a
  3471. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end()); // b
  3472. // but d should still be a placeholder, since we have not declared it yet.
  3473. deps.clear();
  3474. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3475. EXPECT_EQ(deps.size(), 2);
  3476. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3477. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end()); // c
  3478. // now make d exist too and pretend a job came in to process it:
  3479. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile2_Job, QString("tempdata\n"))); // create file D
  3480. AssetProcessorManager::JobToProcessEntry job3;
  3481. job3.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "d.txt");
  3482. job3.m_sourceFileInfo.m_scanFolder = m_scanFolder;
  3483. job3.m_sourceFileInfo.m_uuid = m_uuidOfD;
  3484. m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfD] = SourceAssetReference{ m_watchFolderPath, "d.txt" };
  3485. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job3);
  3486. // all files should now be present:
  3487. deps.clear();
  3488. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3489. EXPECT_EQ(deps.size(), 5);
  3490. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3491. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3492. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end());
  3493. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3494. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end());
  3495. }
  3496. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear)
  3497. {
  3498. // this test makes sure that when files DO appear that were previously placeholders, the database is updated
  3499. // so the strategy here is to have files a, and c missing, which are declared as dependencies by name.
  3500. // then, we make them re-appear later, and check that the database has updated them appropriately.
  3501. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3502. SetupData({ MakeSourceDependency("a.txt"), MakeSourceDependency(m_uuidOfB) }, { MakeJobDependency("c.txt"), MakeJobDependency(m_uuidOfD) }, false, true, true, job);
  3503. // so at this point, the database should be in the same state as after the UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid test
  3504. // which was already verified, by that test.
  3505. // now that the database has placeholders, we expect them to resolve themselves when we provide the actual files:
  3506. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile1_Source, QString("tempdata\n")));
  3507. // now that A exists, we pretend a job came in to process a. (it doesn't require dependencies to be declared)
  3508. AssetProcessorManager::JobToProcessEntry job2;
  3509. job2.m_sourceFileInfo.m_sourceAssetReference = AssetProcessor::SourceAssetReference(m_watchFolderPath, "a.txt");
  3510. job2.m_sourceFileInfo.m_scanFolder = m_scanFolder;
  3511. job2.m_sourceFileInfo.m_uuid = m_uuidOfA;
  3512. m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfA] = SourceAssetReference{ m_watchFolderPath, "a.txt" };
  3513. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job2);
  3514. // a should no longer be a placeholder
  3515. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  3516. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  3517. EXPECT_EQ(deps.size(), 3);
  3518. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3519. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end()); // a
  3520. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end()); // b
  3521. deps.clear();
  3522. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  3523. EXPECT_EQ(deps.size(), 2);
  3524. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3525. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end()); // d
  3526. // now make c exist too and pretend a job came in to process it:
  3527. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_dependsOnFile1_Job, QString("tempdata\n")));
  3528. AssetProcessor::SourceAssetReference cAssetReference(m_watchFolderPath, "c.txt");
  3529. AZ::Uuid uuidOfC = AssetUtilities::GetSourceUuid(cAssetReference).GetValue();
  3530. AssetProcessorManager::JobToProcessEntry job3;
  3531. job3.m_sourceFileInfo.m_sourceAssetReference = cAssetReference;
  3532. job3.m_sourceFileInfo.m_scanFolder = m_scanFolder;
  3533. job3.m_sourceFileInfo.m_uuid = uuidOfC;
  3534. m_assetProcessorManager->m_sourceUUIDToSourceInfoMap[m_uuidOfC] = SourceAssetReference{ m_watchFolderPath, "c.txt" };
  3535. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job3);
  3536. // all files should now be present:
  3537. deps.clear();
  3538. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(m_sourceFileUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any);
  3539. EXPECT_EQ(deps.size(), 5);
  3540. EXPECT_NE(deps.find(m_absPath.toUtf8().constData()), deps.end());
  3541. EXPECT_NE(deps.find(m_dependsOnFile1_Source.toUtf8().constData()), deps.end());
  3542. EXPECT_NE(deps.find(m_dependsOnFile2_Source.toUtf8().constData()), deps.end());
  3543. EXPECT_NE(deps.find(m_dependsOnFile1_Job.toUtf8().constData()), deps.end());
  3544. EXPECT_NE(deps.find(m_dependsOnFile2_Job.toUtf8().constData()), deps.end());
  3545. }
  3546. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_DuplicateSourceDependencies)
  3547. {
  3548. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3549. SetupData({
  3550. MakeSourceDependency("a.txt"),
  3551. MakeSourceDependency("a.txt"),
  3552. MakeSourceDependency(m_uuidOfA),
  3553. MakeSourceDependency(m_uuidOfB),
  3554. MakeSourceDependency(m_uuidOfB) },
  3555. {}, true, true, true, job);
  3556. auto actualDependencies = GetDependencyList();
  3557. EXPECT_THAT(actualDependencies, ::testing::UnorderedElementsAre(
  3558. "a.txt", m_uuidOfA.ToFixedString(false, false).c_str(), m_uuidOfB.ToFixedString(false, false).c_str()
  3559. ));
  3560. }
  3561. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_DuplicateJobDependencies)
  3562. {
  3563. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3564. SetupData({ }, {
  3565. MakeJobDependency("c.txt"),
  3566. MakeJobDependency("c.txt"),
  3567. MakeJobDependency(m_uuidOfC),
  3568. MakeJobDependency(m_uuidOfD),
  3569. },
  3570. true, true, true, job);
  3571. auto actualDependencies = GetDependencyList();
  3572. EXPECT_THAT(
  3573. actualDependencies,
  3574. ::testing::UnorderedElementsAre(
  3575. "c.txt", m_uuidOfC.ToFixedString(false, false).c_str(), m_uuidOfD.ToFixedString(false, false).c_str()));
  3576. }
  3577. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_JobAndSourceDependenciesDuplicated)
  3578. {
  3579. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3580. SetupData(
  3581. {
  3582. MakeSourceDependency("a.txt"),
  3583. MakeSourceDependency(m_uuidOfB)
  3584. },
  3585. {
  3586. MakeJobDependency(m_uuidOfA),
  3587. MakeJobDependency("b.txt"),
  3588. },
  3589. true, true, true, job);
  3590. auto actualDependencies = GetDependencyList();
  3591. EXPECT_THAT(
  3592. actualDependencies,
  3593. ::testing::UnorderedElementsAre(
  3594. "a.txt",
  3595. m_uuidOfA.ToFixedString(false, false).c_str(),
  3596. "b.txt",
  3597. m_uuidOfB.ToFixedString(false, false).c_str()));
  3598. }
  3599. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_SourceDependenciesDuplicatedWildcard)
  3600. {
  3601. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3602. SetupData(
  3603. {
  3604. MakeSourceDependency("a.txt"),
  3605. MakeSourceDependency("a.t*t", true),
  3606. MakeSourceDependency(m_uuidOfB),
  3607. }, {}, true, true, true, job);
  3608. auto actualDependencies = GetDependencyList();
  3609. EXPECT_THAT(
  3610. actualDependencies,
  3611. ::testing::UnorderedElementsAre(
  3612. "a.txt",
  3613. "a.t%t",
  3614. m_uuidOfB.ToFixedString(false, false).c_str()));
  3615. }
  3616. TEST_F(SourceFileDependenciesTest, UpdateSourceFileDependenciesDatabase_AbsolutePathIsPreserved)
  3617. {
  3618. QDir tempPath(m_assetRootDir.path());
  3619. QString absPath = tempPath.absoluteFilePath("subfolder1/a.txt");
  3620. AssetProcessor::AssetProcessorManager::JobToProcessEntry job;
  3621. SetupData(
  3622. {
  3623. MakeSourceDependency(absPath.toUtf8().constData()),
  3624. },
  3625. {},
  3626. true,
  3627. true,
  3628. true,
  3629. job);
  3630. auto actualDependencies = GetDependencyList();
  3631. EXPECT_THAT(actualDependencies, ::testing::UnorderedElementsAre(absPath.toUtf8().constData()));
  3632. }
  3633. TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK)
  3634. {
  3635. using namespace AssetProcessor;
  3636. using namespace AssetBuilderSDK;
  3637. QString watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  3638. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
  3639. ASSERT_NE(scanFolder, nullptr);
  3640. const char relSourceFileName[] = "a.dummy";
  3641. const char secondRelSourceFile[] = "b.dummy";
  3642. QString sourceFileName = m_assetRootDir.absoluteFilePath("subfolder1/a.dummy");
  3643. QString secondSourceFile = m_assetRootDir.absoluteFilePath("subfolder1/b.dummy");
  3644. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourceFileName, QString("tempdata\n")));
  3645. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("tempdata\n")));
  3646. AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
  3647. builderDescriptor.m_name = "Test Dummy Builder";
  3648. builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.dummy", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  3649. builderDescriptor.m_busId = AZ::Uuid::CreateRandom();
  3650. builderDescriptor.m_createJobFunction = [&](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  3651. {
  3652. AssetBuilderSDK::JobDescriptor jobDescriptor;
  3653. jobDescriptor.m_jobKey = builderDescriptor.m_name;
  3654. jobDescriptor.SetPlatformIdentifier("pc");
  3655. if (AzFramework::StringFunc::EndsWith(request.m_sourceFile.c_str(), relSourceFileName))
  3656. {
  3657. AssetBuilderSDK::SourceFileDependency dep = { secondRelSourceFile , AZ::Uuid::CreateNull() };
  3658. AssetBuilderSDK::JobDependency jobDep(builderDescriptor.m_name, "pc", AssetBuilderSDK::JobDependencyType::OrderOnce, dep);
  3659. jobDescriptor.m_jobDependencyList.emplace_back(jobDep);
  3660. }
  3661. response.m_createJobOutputs.emplace_back(jobDescriptor);
  3662. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  3663. };
  3664. builderDescriptor.m_processJobFunction = [](const AssetBuilderSDK::ProcessJobRequest& /*request*/, AssetBuilderSDK::ProcessJobResponse& response)
  3665. {
  3666. response.m_resultCode = AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success;
  3667. };
  3668. MockApplicationManager::BuilderFilePatternMatcherAndBuilderDesc builderFilePatternMatcher;
  3669. builderFilePatternMatcher.m_builderDesc = builderDescriptor;
  3670. builderFilePatternMatcher.m_internalBuilderName = builderDescriptor.m_name;
  3671. builderFilePatternMatcher.m_internalUuid = builderDescriptor.m_busId;
  3672. builderFilePatternMatcher.m_matcherBuilderPattern = AssetUtilities::BuilderFilePatternMatcher(builderDescriptor.m_patterns.back(), builderDescriptor.m_busId);
  3673. m_mockApplicationManager->m_matcherBuilderPatterns.emplace_back(builderFilePatternMatcher);
  3674. // Capture the job details as the APM inspects the file.
  3675. AZStd::vector<JobDetails> jobDetails;
  3676. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
  3677. {
  3678. jobDetails.emplace_back(job);
  3679. });
  3680. // Tell the APM about the file:
  3681. m_isIdling = false;
  3682. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileName));
  3683. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3684. ASSERT_TRUE(BlockUntilIdle(5000));
  3685. // Although we have processed a.dummy first, APM should send us notification of b.dummy job first and than of a.dummy job
  3686. EXPECT_EQ(jobDetails.size(), 2);
  3687. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3688. EXPECT_EQ(jobDetails[1].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
  3689. EXPECT_EQ(jobDetails[1].m_jobDependencyList.size(), 1); // there should only be one job dependency
  3690. EXPECT_EQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, secondSourceFile.toUtf8().constData()); // there should only be one job dependency
  3691. // Process jobs in APM
  3692. auto destination = jobDetails[0].m_cachePath;
  3693. QString productAFileName = (destination / "aoutput.txt").AsPosix().c_str();
  3694. QString productBFileName = (destination / "boutput.txt").AsPosix().c_str();
  3695. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(productBFileName, QString("tempdata\n")));
  3696. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(productAFileName, QString("tempdata\n")));
  3697. AssetBuilderSDK::ProcessJobResponse responseB;
  3698. responseB.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  3699. responseB.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("boutput.txt", AZ::Uuid::CreateNull(), 1));
  3700. AssetBuilderSDK::ProcessJobResponse responseA;
  3701. responseA.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  3702. responseA.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("aoutput.txt", AZ::Uuid::CreateNull(), 1));
  3703. m_isIdling = false;
  3704. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseB));
  3705. ASSERT_TRUE(BlockUntilIdle(5000));
  3706. m_isIdling = false;
  3707. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseA));
  3708. ASSERT_TRUE(BlockUntilIdle(5000));
  3709. jobDetails.clear();
  3710. m_isIdling = false;
  3711. // Modify source file b.dummy, we should only see one job with source file b.dummy getting processed again even though a.dummy job has an order once job dependency on it .
  3712. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("temp\n")));
  3713. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3714. ASSERT_TRUE(BlockUntilIdle(5000));
  3715. EXPECT_EQ(jobDetails.size(), 1);
  3716. EXPECT_STREQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile.toUtf8().constData());
  3717. jobDetails.clear();
  3718. m_isIdling = false;
  3719. // Modify source file a.dummy, we should only see one job with source file a.dummy getting processed in this case.
  3720. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourceFileName, QString("temp\n")));
  3721. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileName));
  3722. ASSERT_TRUE(BlockUntilIdle(5000));
  3723. EXPECT_EQ(jobDetails.size(), 1);
  3724. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
  3725. EXPECT_EQ(jobDetails[0].m_jobDependencyList.size(), 0); // there should not be any job dependency since APM has already processed b.dummy before
  3726. m_isIdling = false;
  3727. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseA));
  3728. ASSERT_TRUE(BlockUntilIdle(5000));
  3729. jobDetails.clear();
  3730. m_isIdling = false;
  3731. // Here first fail the b.dummy job and than tell APM about the modified file
  3732. // This should cause a.dummy job to get emitted again
  3733. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("tempData\n")));
  3734. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3735. ASSERT_TRUE(BlockUntilIdle(5000));
  3736. EXPECT_EQ(jobDetails.size(), 1);
  3737. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3738. responseB.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  3739. m_isIdling = false;
  3740. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetFailed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry));
  3741. ASSERT_TRUE(BlockUntilIdle(5000));
  3742. jobDetails.clear();
  3743. m_isIdling = false;
  3744. // Modify source file b.dummy
  3745. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("temp\n")));
  3746. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3747. ASSERT_TRUE(BlockUntilIdle(5000));
  3748. EXPECT_EQ(jobDetails.size(), 2);
  3749. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3750. EXPECT_EQ(jobDetails[1].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
  3751. EXPECT_EQ(jobDetails[1].m_jobDependencyList.size(), 1); // there should only be one job dependency
  3752. EXPECT_STREQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str(), secondSourceFile.toUtf8().constData()); // there should only be one job dependency
  3753. }
  3754. TEST_F(AssetProcessorManagerTest, JobDependencyOrderOnly_MultipleJobs_EmitOK)
  3755. {
  3756. using namespace AssetProcessor;
  3757. using namespace AssetBuilderSDK;
  3758. QString watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  3759. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
  3760. ASSERT_NE(scanFolder, nullptr);
  3761. const char relSourceFileName[] = "a.dummy";
  3762. const char secondRelSourceFile[] = "b.dummy";
  3763. QString sourceFileName = m_assetRootDir.absoluteFilePath("subfolder1/a.dummy");
  3764. QString secondSourceFile = m_assetRootDir.absoluteFilePath("subfolder1/b.dummy");
  3765. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourceFileName, QString("tempdata\n")));
  3766. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("tempdata\n")));
  3767. AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
  3768. builderDescriptor.m_name = "Test Dummy Builder";
  3769. builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.dummy", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  3770. builderDescriptor.m_busId = AZ::Uuid::CreateRandom();
  3771. builderDescriptor.m_createJobFunction = [&](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  3772. {
  3773. AssetBuilderSDK::JobDescriptor jobDescriptor;
  3774. jobDescriptor.m_jobKey = builderDescriptor.m_name;
  3775. jobDescriptor.SetPlatformIdentifier("pc");
  3776. if (AzFramework::StringFunc::EndsWith(request.m_sourceFile.c_str(), relSourceFileName))
  3777. {
  3778. AssetBuilderSDK::SourceFileDependency dep = { secondRelSourceFile , AZ::Uuid::CreateNull() };
  3779. AssetBuilderSDK::JobDependency jobDep(builderDescriptor.m_name, "pc", AssetBuilderSDK::JobDependencyType::OrderOnly, dep);
  3780. jobDescriptor.m_jobDependencyList.emplace_back(jobDep);
  3781. }
  3782. response.m_createJobOutputs.emplace_back(jobDescriptor);
  3783. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  3784. };
  3785. builderDescriptor.m_processJobFunction = [](const AssetBuilderSDK::ProcessJobRequest& /*request*/, AssetBuilderSDK::ProcessJobResponse& response)
  3786. {
  3787. response.m_resultCode = AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success;
  3788. };
  3789. MockApplicationManager::BuilderFilePatternMatcherAndBuilderDesc builderFilePatternMatcher;
  3790. builderFilePatternMatcher.m_builderDesc = builderDescriptor;
  3791. builderFilePatternMatcher.m_internalBuilderName = builderDescriptor.m_name;
  3792. builderFilePatternMatcher.m_internalUuid = builderDescriptor.m_busId;
  3793. builderFilePatternMatcher.m_matcherBuilderPattern = AssetUtilities::BuilderFilePatternMatcher(builderDescriptor.m_patterns.back(), builderDescriptor.m_busId);
  3794. m_mockApplicationManager->m_matcherBuilderPatterns.emplace_back(builderFilePatternMatcher);
  3795. // Capture the job details as the APM inspects the file.
  3796. AZStd::vector<JobDetails> jobDetails;
  3797. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
  3798. {
  3799. jobDetails.emplace_back(job);
  3800. });
  3801. // Tell the APM about the file:
  3802. m_isIdling = false;
  3803. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileName));
  3804. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3805. ASSERT_TRUE(BlockUntilIdle(5000));
  3806. // Although we have processed a.dummy first, APM should send us notification of b.dummy job first and than of a.dummy job
  3807. EXPECT_EQ(jobDetails.size(), 2);
  3808. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3809. EXPECT_EQ(jobDetails[1].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
  3810. EXPECT_EQ(jobDetails[1].m_jobDependencyList.size(), 1); // there should only be one job dependency
  3811. EXPECT_EQ(jobDetails[1].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, secondSourceFile.toUtf8().constData()); // there should only be one job dependency
  3812. // Process jobs in APM
  3813. auto destination = jobDetails[0].m_cachePath;
  3814. QString productAFileName = (destination / "aoutput.txt").AsPosix().c_str();
  3815. QString productBFileName = (destination / "boutput.txt").AsPosix().c_str();
  3816. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(productBFileName, QString("tempdata\n")));
  3817. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(productAFileName, QString("tempdata\n")));
  3818. AssetBuilderSDK::ProcessJobResponse responseB;
  3819. responseB.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  3820. responseB.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("boutput.txt", AZ::Uuid::CreateNull(), 1));
  3821. AssetBuilderSDK::ProcessJobResponse responseA;
  3822. responseA.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  3823. responseA.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("aoutput.txt", AZ::Uuid::CreateNull(), 1));
  3824. m_isIdling = false;
  3825. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseB));
  3826. ASSERT_TRUE(BlockUntilIdle(5000));
  3827. m_isIdling = false;
  3828. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseA));
  3829. ASSERT_TRUE(BlockUntilIdle(5000));
  3830. jobDetails.clear();
  3831. m_isIdling = false;
  3832. // Modify source file b.dummy, we should only see one job with source file b.dummy getting processed again because a.dummy job has an order only job dependency on it .
  3833. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("temp\n")));
  3834. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3835. ASSERT_TRUE(BlockUntilIdle(5000));
  3836. EXPECT_EQ(jobDetails.size(), 1);
  3837. EXPECT_STREQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile.toUtf8().constData());
  3838. jobDetails.clear();
  3839. m_isIdling = false;
  3840. // Modify source file a.dummy, we should only see one job with source file a.dummy getting processed in this case.
  3841. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourceFileName, QString("temp\n")));
  3842. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileName));
  3843. ASSERT_TRUE(BlockUntilIdle(5000));
  3844. EXPECT_EQ(jobDetails.size(), 1);
  3845. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), sourceFileName);
  3846. EXPECT_EQ(jobDetails[0].m_jobDependencyList.size(), 1); // there should be one job dependency since APM has already processed b.dummy before but a.dummy has OrderOnly dependency on it.
  3847. m_isIdling = false;
  3848. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, responseA));
  3849. ASSERT_TRUE(BlockUntilIdle(5000));
  3850. jobDetails.clear();
  3851. m_isIdling = false;
  3852. // Here first fail the b.dummy job and than tell APM about the modified file
  3853. // This should NOT cause a.dummy job to get emitted again
  3854. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("tempData\n")));
  3855. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3856. ASSERT_TRUE(BlockUntilIdle(5000));
  3857. EXPECT_EQ(jobDetails.size(), 1);
  3858. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3859. responseB.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  3860. m_isIdling = false;
  3861. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetFailed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetails[0].m_jobEntry));
  3862. ASSERT_TRUE(BlockUntilIdle(5000));
  3863. jobDetails.clear();
  3864. m_isIdling = false;
  3865. // Modify source file b.dummy
  3866. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(secondSourceFile, QString("temp\n")));
  3867. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, secondSourceFile));
  3868. ASSERT_TRUE(BlockUntilIdle(5000));
  3869. EXPECT_EQ(jobDetails.size(), 1);
  3870. EXPECT_EQ(jobDetails[0].m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), secondSourceFile);
  3871. }
  3872. TEST_F(AssetProcessorManagerTest, SourceFile_With_NonASCII_Characters_Job_OK)
  3873. {
  3874. // This test ensures that asset processor manager detects a source file that has non-ASCII characters
  3875. // and sends a notification for a dummy autofail job.
  3876. // This test also ensure that when we get a folder delete notification, it forwards the relative folder path to the GUI model for removal of jobs.
  3877. QString deletedFolderPath;
  3878. QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::SourceFolderDeleted,
  3879. [&deletedFolderPath](QString folderPath)
  3880. {
  3881. deletedFolderPath = folderPath;
  3882. });
  3883. JobDetails failedjobDetails;
  3884. QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetToProcess,
  3885. [&failedjobDetails](JobDetails jobDetails)
  3886. {
  3887. failedjobDetails = jobDetails;
  3888. });
  3889. QString watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  3890. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
  3891. ASSERT_NE(scanFolder, nullptr);
  3892. QString folderPath(m_assetRootDir.absoluteFilePath(u8"subfolder1/Test\u2133")); // u+2133 -> "Script Capital M" unicode character
  3893. QDir folderPathDir(folderPath);
  3894. QString absPath(folderPathDir.absoluteFilePath(u8"Test\u21334.txt"));
  3895. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(absPath, QString("test\n")));
  3896. m_assetProcessorManager.get()->AssessAddedFile(absPath);
  3897. ASSERT_TRUE(BlockUntilIdle(5000));
  3898. EXPECT_EQ(failedjobDetails.m_autoFail, false);
  3899. EXPECT_EQ(failedjobDetails.m_jobEntry.GetAbsoluteSourcePath(), absPath);
  3900. // folder delete notification
  3901. folderPathDir.removeRecursively();
  3902. m_assetProcessorManager.get()->AssessDeletedFile(folderPath);
  3903. ASSERT_TRUE(BlockUntilIdle(5000));
  3904. EXPECT_EQ(deletedFolderPath, folderPath);
  3905. }
  3906. TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint)
  3907. {
  3908. constexpr int idleWaitTime = 5000;
  3909. using namespace AzToolsFramework::AssetDatabase;
  3910. QList<AssetProcessor::JobDetails> processResults;
  3911. auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&processResults](JobDetails details)
  3912. {
  3913. processResults.push_back(AZStd::move(details));
  3914. });
  3915. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(m_assetRootDir.absoluteFilePath("subfolder1"));
  3916. ASSERT_NE(scanFolder, nullptr);
  3917. QString absPath = m_assetRootDir.absoluteFilePath("subfolder1/test.txt");
  3918. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(absPath, QString("test\n")));
  3919. //////////////////////////////////////////////////////////////////////////
  3920. // Add a file and signal a successful process event
  3921. m_assetProcessorManager.get()->AssessAddedFile(absPath);
  3922. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  3923. for(const auto& processResult : processResults)
  3924. {
  3925. AZStd::string file = AZStd::string(processResult.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()) + ".arc1";
  3926. // Create the file on disk
  3927. ASSERT_TRUE(UnitTestUtils::CreateDummyFile((processResult.m_cachePath / file).AsPosix().c_str(), "products."));
  3928. AssetBuilderSDK::ProcessJobResponse response;
  3929. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  3930. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file, AZ::Uuid::CreateNull(), 1));
  3931. m_assetProcessorManager->AssetProcessed(processResult.m_jobEntry, response);
  3932. }
  3933. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  3934. bool found = false;
  3935. SourceDatabaseEntry source;
  3936. auto queryFunc = [&](SourceDatabaseEntry& sourceData)
  3937. {
  3938. source = AZStd::move(sourceData);
  3939. found = true;
  3940. return false; // stop iterating after the first one. There should actually only be one entry anyway.
  3941. };
  3942. m_assetProcessorManager->m_stateData->QuerySourceBySourceNameScanFolderID("test.txt", scanFolder->ScanFolderID(), queryFunc);
  3943. ASSERT_TRUE(found);
  3944. ASSERT_NE(source.m_analysisFingerprint, "");
  3945. // Modify the file and run it through AP again, but this time signal a failure
  3946. {
  3947. QFile writer(absPath);
  3948. ASSERT_TRUE(writer.open(QFile::WriteOnly));
  3949. QTextStream ts(&writer);
  3950. ts.setCodec("UTF-8");
  3951. ts << "Hello World";
  3952. }
  3953. processResults.clear();
  3954. m_assetProcessorManager.get()->AssessModifiedFile(absPath);
  3955. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  3956. for (const auto& processResult : processResults)
  3957. {
  3958. m_assetProcessorManager->AssetFailed(processResult.m_jobEntry);
  3959. }
  3960. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  3961. // Check the database, the fingerprint should be erased since the file failed
  3962. found = false;
  3963. m_assetProcessorManager->m_stateData->QuerySourceBySourceNameScanFolderID("test.txt", scanFolder->ScanFolderID(), queryFunc);
  3964. ASSERT_TRUE(found);
  3965. ASSERT_EQ(source.m_analysisFingerprint, "");
  3966. }
  3967. TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ValidLfsPointerFile_ReceiveLFSPointerFileError)
  3968. {
  3969. // Override the project and engine root directories in the setting registry to create a custom .gitattributes file for testing.
  3970. auto settingsRegistry = AZ::SettingsRegistry::Get();
  3971. ASSERT_TRUE(settingsRegistry);
  3972. AZ::IO::FixedMaxPathString engineRoot, projectRoot;
  3973. settingsRegistry->Get(engineRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  3974. settingsRegistry->Get(projectRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  3975. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, m_assetRootDir.path().toUtf8().data());
  3976. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, m_assetRootDir.path().toUtf8().data());
  3977. QString gitAttributesPath = m_assetRootDir.absoluteFilePath(".gitattributes");
  3978. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(gitAttributesPath, QString(
  3979. "#\n"
  3980. "# Git LFS(see https ://git-lfs.github.com/)\n"
  3981. "#\n"
  3982. "*.txt filter=lfs diff=lfs merge=lfs -text\n")));
  3983. QString sourcePath = m_assetRootDir.absoluteFilePath("subfolder1/test.txt");
  3984. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourcePath, QString(
  3985. "version https://git-lfs.github.com/spec/v1\n"
  3986. "oid sha256:ee4799379bfcfa99e95afd6494da51fbeda95f21ea71d267ae7102f048edec85\n"
  3987. "size 63872\n")));
  3988. constexpr int idleWaitTime = 5000;
  3989. using namespace AzToolsFramework::AssetDatabase;
  3990. QList<AssetProcessor::JobDetails> processResults;
  3991. auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&processResults](JobDetails details)
  3992. {
  3993. processResults.push_back(AZStd::move(details));
  3994. });
  3995. // Add the test file and signal a failed event
  3996. m_assetProcessorManager.get()->AssessAddedFile(sourcePath);
  3997. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  3998. for(const auto& processResult : processResults)
  3999. {
  4000. m_assetProcessorManager->AssetFailed(processResult.m_jobEntry);
  4001. }
  4002. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  4003. // An error message should be thrown for the valid LFS pointer file.
  4004. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
  4005. // Revert the project and engine root directories in the setting registry.
  4006. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, engineRoot);
  4007. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, projectRoot);
  4008. }
  4009. TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_AutoFailedLfsPointerFile_ReceiveLFSPointerFileError)
  4010. {
  4011. // Override the project and engine root directories in the setting registry to create a custom .gitattributes file for testing.
  4012. auto settingsRegistry = AZ::SettingsRegistry::Get();
  4013. ASSERT_TRUE(settingsRegistry);
  4014. AZ::IO::FixedMaxPathString engineRoot, projectRoot;
  4015. settingsRegistry->Get(engineRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  4016. settingsRegistry->Get(projectRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  4017. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, m_assetRootDir.path().toUtf8().data());
  4018. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, m_assetRootDir.path().toUtf8().data());
  4019. QDir assetRootDir(m_assetRootDir.path());
  4020. QString gitAttributesPath = assetRootDir.absoluteFilePath(".gitattributes");
  4021. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(gitAttributesPath, QString(
  4022. "#\n"
  4023. "# Git LFS(see https ://git-lfs.github.com/)\n"
  4024. "#\n"
  4025. "*.txt filter=lfs diff=lfs merge=lfs -text\n")));
  4026. QString sourcePath = assetRootDir.absoluteFilePath("subfolder1/test.txt");
  4027. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourcePath, QString(
  4028. "version https://git-lfs.github.com/spec/v1\n"
  4029. "oid sha256:ee4799379bfcfa99e95afd6494da51fbeda95f21ea71d267ae7102f048edec85\n"
  4030. "size 63872\n")));
  4031. constexpr int idleWaitTime = 5000;
  4032. using namespace AzToolsFramework::AssetDatabase;
  4033. QList<AssetProcessor::JobDetails> processResults;
  4034. auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&processResults](JobDetails details)
  4035. {
  4036. details.m_jobEntry.m_addToDatabase = false;
  4037. processResults.push_back(AZStd::move(details));
  4038. });
  4039. // Add the test file and signal a failed event
  4040. m_assetProcessorManager.get()->AssessAddedFile(sourcePath);
  4041. ASSERT_TRUE(BlockUntilIdle(idleWaitTime));
  4042. for(const auto& processResult : processResults)
  4043. {
  4044. m_assetProcessorManager->AssetFailed(processResult.m_jobEntry);
  4045. }
  4046. // An error message should be thrown for the valid LFS pointer file.
  4047. ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
  4048. // Revert the project and engine root directories in the setting registry.
  4049. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, engineRoot);
  4050. settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, projectRoot);
  4051. }
  4052. //////////////////////////////////////////////////////////////////////////
  4053. void FingerprintTest::SetUp()
  4054. {
  4055. AssetProcessorManagerTest::SetUp();
  4056. // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
  4057. m_mockApplicationManager->BusDisconnect();
  4058. // Create the test file
  4059. const auto& scanFolder = m_config->GetScanFolderAt(1);
  4060. QString relativePathFromWatchFolder("fingerprintTest.txt");
  4061. m_absolutePath = QDir(scanFolder.ScanPath()).absoluteFilePath(relativePathFromWatchFolder);
  4062. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails jobDetails)
  4063. {
  4064. m_jobResults.push_back(jobDetails);
  4065. });
  4066. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_absolutePath, ""));
  4067. }
  4068. void FingerprintTest::TearDown()
  4069. {
  4070. m_jobResults = AZStd::vector<AssetProcessor::JobDetails>{};
  4071. m_mockBuilderInfoHandler = {};
  4072. AssetProcessorManagerTest::TearDown();
  4073. }
  4074. void FingerprintTest::RunFingerprintTest(QString builderFingerprint, QString jobFingerprint, bool expectedResult)
  4075. {
  4076. m_mockBuilderInfoHandler.CreateBuilderDesc(
  4077. "test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}",
  4078. { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) },
  4079. UnitTests::MockMultiBuilderInfoHandler::AssetBuilderExtraInfo{ jobFingerprint, "", "", builderFingerprint, {} });
  4080. m_mockBuilderInfoHandler.BusConnect();
  4081. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_absolutePath));
  4082. ASSERT_TRUE(BlockUntilIdle(5000));
  4083. ASSERT_EQ(m_mockBuilderInfoHandler.m_createJobsCount, 1);
  4084. ASSERT_EQ(m_jobResults.size(), 1);
  4085. ASSERT_EQ(m_jobResults[0].m_autoFail, expectedResult);
  4086. }
  4087. TEST_F(FingerprintTest, FingerprintChecking_JobFingerprint_NoBuilderFingerprint)
  4088. {
  4089. RunFingerprintTest("", "Hello World", true);
  4090. }
  4091. TEST_F(FingerprintTest, FingerprintChecking_NoJobFingerprint_NoBuilderFingerprint)
  4092. {
  4093. RunFingerprintTest("", "", false);
  4094. }
  4095. TEST_F(FingerprintTest, FingerprintChecking_JobFingerprint_BuilderFingerprint)
  4096. {
  4097. RunFingerprintTest("Hello", "World", false);
  4098. }
  4099. TEST_F(FingerprintTest, FingerprintChecking_NoJobFingerprint_BuilderFingerprint)
  4100. {
  4101. RunFingerprintTest("Hello World", "", false);
  4102. }
  4103. TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear)
  4104. {
  4105. // This test checks that wildcard source dependencies are added to the database as "SourceLikeMatch",
  4106. // find existing files which match the dependency and add them as either job or source file dependencies,
  4107. // And recognize matching files as dependencies
  4108. // Cache does not handle mixed dependency types
  4109. m_assetProcessorManager->m_dependencyCacheEnabled = false;
  4110. AZ::Uuid dummyBuilderUUID = AZ::Uuid::CreateRandom();
  4111. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder1/wildcardTest.txt"));
  4112. QString relFileName("wildcardTest.txt");
  4113. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/wildcardTest.txt"));
  4114. QString watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  4115. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
  4116. ASSERT_NE(scanFolder, nullptr);
  4117. // the above file (assetProcessorManagerTest.txt) will depend on these four files:
  4118. QString dependsOnFilea_Source = m_assetRootDir.absoluteFilePath("subfolder1/a.txt");
  4119. QString dependsOnFileb_Source = m_assetRootDir.absoluteFilePath("subfolder1/b.txt");
  4120. QString dependsOnFileb1_Source = m_assetRootDir.absoluteFilePath("subfolder1/b1.txt");
  4121. QString dependsOnFilec_Job = m_assetRootDir.absoluteFilePath("subfolder1/c.txt");
  4122. QString dependsOnFilec1_Job = m_assetRootDir.absoluteFilePath("subfolder1/c1.txt");
  4123. QString dependsOnFiled_Job = m_assetRootDir.absoluteFilePath("subfolder1/d.txt");
  4124. // in this case, we are only creating file b, and d, which are addressed by UUID.
  4125. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFileb_Source, QString("tempdata\n")));
  4126. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFilec_Job, QString("tempdata\n")));
  4127. // construct the dummy job to feed to the database updater function:
  4128. AssetProcessor::SourceAssetReference sourceAsset(absPath);
  4129. AZ::Uuid wildcardTestUuid = AssetUtilities::GetSourceUuid(sourceAsset).GetValue();
  4130. AssetProcessorManager::JobToProcessEntry job;
  4131. job.m_sourceFileInfo.m_sourceAssetReference = sourceAsset;
  4132. job.m_sourceFileInfo.m_scanFolder = scanFolder;
  4133. job.m_sourceFileInfo.m_uuid = wildcardTestUuid;
  4134. // each file we will take a different approach to publishing: rel path, and UUID:
  4135. job.m_sourceFileDependencies.emplace_back(dummyBuilderUUID, AssetBuilderSDK::SourceFileDependency{ "b*.txt", AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards });
  4136. // it is currently assumed that the only fields that we care about in JobDetails is the builder busId and the job dependencies themselves:
  4137. JobDetails newDetails;
  4138. newDetails.m_assetBuilderDesc.m_busId = dummyBuilderUUID;
  4139. AssetBuilderSDK::SourceFileDependency dep1 = { "c*.txt", AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards };
  4140. AssetBuilderSDK::JobDependency jobDep1("pc build", "pc", AssetBuilderSDK::JobDependencyType::Order, dep1);
  4141. newDetails.m_jobDependencyList.push_back(JobDependencyInternal(jobDep1));
  4142. job.m_jobsToAnalyze.push_back(newDetails);
  4143. m_assetProcessorManager.get()->UpdateSourceFileDependenciesDatabase(job);
  4144. AzToolsFramework::AssetDatabase::SourceDatabaseEntry wildcard(scanFolder->ScanFolderID(), "wildcardTest.txt", wildcardTestUuid, "fingerprint");
  4145. m_assetProcessorManager->m_stateData->SetSource(wildcard);
  4146. AssetProcessor::SourceFilesForFingerprintingContainer deps;
  4147. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(wildcardTestUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource);
  4148. EXPECT_EQ(deps.size(), 2);
  4149. EXPECT_NE(deps.find(dependsOnFileb_Source.toUtf8().constData()), deps.end());
  4150. deps.clear();
  4151. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(wildcardTestUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_JobToJob);
  4152. EXPECT_EQ(deps.size(), 2);
  4153. EXPECT_NE(deps.find(dependsOnFilec_Job.toUtf8().constData()), deps.end());
  4154. deps.clear();
  4155. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(wildcardTestUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceOrJob);
  4156. EXPECT_EQ(deps.size(), 3);
  4157. EXPECT_NE(deps.find(dependsOnFilec_Job.toUtf8().constData()), deps.end());
  4158. EXPECT_NE(deps.find(dependsOnFileb_Source.toUtf8().constData()), deps.end());
  4159. deps.clear();
  4160. m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(wildcardTestUuid, deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch);
  4161. EXPECT_EQ(deps.size(), 1);
  4162. deps.clear();
  4163. AZStd::vector<AZStd::string> wildcardDeps;
  4164. auto callbackFunction = [&wildcardDeps](AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& entry)
  4165. {
  4166. wildcardDeps.push_back(entry.m_dependsOnSource.ToString());
  4167. return true;
  4168. };
  4169. m_assetProcessorManager.get()->m_stateData->QueryDependsOnSourceBySourceDependency(wildcardTestUuid, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, callbackFunction);
  4170. EXPECT_EQ(wildcardDeps.size(), 2);
  4171. // The database should have the wildcard record and the individual dependency on b and c at this point, now we add new files
  4172. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFileb1_Source, QString("tempdata\n")));
  4173. ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFilec1_Job, QString("tempdata\n")));
  4174. QStringList dependList;
  4175. dependList = m_assetProcessorManager.get()->GetSourceFilesWhichDependOnSourceFile(dependsOnFileb1_Source, {});
  4176. EXPECT_EQ(dependList.size(), 1);
  4177. EXPECT_EQ(dependList[0], absPath.toUtf8().constData());
  4178. dependList.clear();
  4179. dependList = m_assetProcessorManager.get()->GetSourceFilesWhichDependOnSourceFile(dependsOnFilec1_Job, {});
  4180. EXPECT_EQ(dependList.size(), 1);
  4181. EXPECT_EQ(dependList[0], absPath.toUtf8().constData());
  4182. dependList.clear();
  4183. dependList = m_assetProcessorManager.get()->GetSourceFilesWhichDependOnSourceFile(dependsOnFilea_Source, {});
  4184. EXPECT_EQ(dependList.size(), 0);
  4185. dependList.clear();
  4186. dependList = m_assetProcessorManager.get()->GetSourceFilesWhichDependOnSourceFile(dependsOnFiled_Job, {});
  4187. EXPECT_EQ(dependList.size(), 0);
  4188. dependList.clear();
  4189. }
  4190. TEST_F(AssetProcessorManagerTest, RemoveSource_RemoveCacheFolderIfEmpty_Ok)
  4191. {
  4192. using namespace AssetProcessor;
  4193. using namespace AssetBuilderSDK;
  4194. QStringList sourceFiles;
  4195. QStringList productFiles;
  4196. // Capture the job details as the APM inspects the file.
  4197. JobDetails jobDetails;
  4198. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
  4199. {
  4200. jobDetails = job;
  4201. });
  4202. static constexpr int NumOfSourceFiles = 2;
  4203. for (int idx = 0; idx < NumOfSourceFiles; idx++)
  4204. {
  4205. sourceFiles.append(m_assetRootDir.absoluteFilePath("subfolder1/subfolder2/source_test%1.txt").arg(idx));
  4206. UnitTestUtils::CreateDummyFile(sourceFiles[idx], "source");
  4207. // Tell the APM about the file:
  4208. m_isIdling = false;
  4209. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFiles[idx]));
  4210. ASSERT_TRUE(BlockUntilIdle(5000));
  4211. auto filename = AZStd::string::format("product_test%d.txt", idx);
  4212. productFiles.append((jobDetails.m_cachePath / filename).AsPosix().c_str());
  4213. UnitTestUtils::CreateDummyFile(productFiles.back(), "product");
  4214. // Populate ProcessJobResponse
  4215. ProcessJobResponse response;
  4216. response.m_resultCode = ProcessJobResult_Success;
  4217. JobProduct product((jobDetails.m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(idx));
  4218. response.m_outputProducts.push_back(product);
  4219. // Process the job
  4220. m_isIdling = false;
  4221. m_assetProcessorManager->AssetProcessed(jobDetails.m_jobEntry, response);
  4222. ASSERT_TRUE(BlockUntilIdle(5000));
  4223. }
  4224. QObject::disconnect(connection);
  4225. // ----------------------------- TEST BEGINS HERE -----------------------------
  4226. // We have two source files that create products in the same cache directory.
  4227. // Deleting the first source file should only remove products associated with it
  4228. // Deleting the second source should remove the cache directory along with all products associated with it.
  4229. int firstSourceIdx = 0;
  4230. AZ::IO::SystemFile::Delete(sourceFiles[firstSourceIdx].toUtf8().data());
  4231. m_isIdling = false;
  4232. // Simulate the file watcher notifying a file delete:
  4233. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFiles[firstSourceIdx]));
  4234. ASSERT_TRUE(BlockUntilIdle(5000));
  4235. // Ensure that products no longer exists on disk
  4236. ASSERT_FALSE(QFile::exists(productFiles[firstSourceIdx]));
  4237. // Ensure that cache directory exists
  4238. QDir cacheDirectory(jobDetails.m_cachePath.AsPosix().c_str());
  4239. ASSERT_TRUE(cacheDirectory.exists());
  4240. int secondSourceIdx = 1;
  4241. AZ::IO::SystemFile::Delete(sourceFiles[secondSourceIdx].toUtf8().data());
  4242. m_isIdling = false;
  4243. // Simulate the file watcher notifying a file delete:
  4244. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFiles[secondSourceIdx]));
  4245. ASSERT_TRUE(BlockUntilIdle(5000));
  4246. // Ensure that products no longer exists on disk
  4247. ASSERT_FALSE(QFile::exists(productFiles[secondSourceIdx]));
  4248. // Ensure that cache directory is removed this time
  4249. ASSERT_FALSE(cacheDirectory.exists());
  4250. }
  4251. void DuplicateProductsTest::SetupDuplicateProductsTest(QString& sourceFile, QDir& tempPath, QString& productFile, AZStd::vector<AssetProcessor::JobDetails>& jobDetails, AssetBuilderSDK::ProcessJobResponse& response, bool multipleOutputs, QString extension)
  4252. {
  4253. using namespace AssetProcessor;
  4254. using namespace AssetBuilderSDK;
  4255. // Capture the job details as the APM inspects the file.
  4256. QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
  4257. {
  4258. jobDetails.emplace_back(job);
  4259. });
  4260. AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
  4261. builderDescriptor.m_name = "Test Txt Builder";
  4262. builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(QString("*.%1").arg(extension).toUtf8().constData(), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  4263. builderDescriptor.m_busId = AZ::Uuid::CreateRandom();
  4264. builderDescriptor.m_createJobFunction = [&](const AssetBuilderSDK::CreateJobsRequest& /*request*/, AssetBuilderSDK::CreateJobsResponse& response)
  4265. {
  4266. AssetBuilderSDK::JobDescriptor jobDescriptor;
  4267. jobDescriptor.m_jobKey = builderDescriptor.m_name;
  4268. jobDescriptor.SetPlatformIdentifier("pc");
  4269. response.m_createJobOutputs.emplace_back(jobDescriptor);
  4270. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  4271. if(multipleOutputs)
  4272. {
  4273. jobDescriptor.m_jobKey = "Duplicate Output";
  4274. response.m_createJobOutputs.emplace_back(jobDescriptor);
  4275. }
  4276. };
  4277. builderDescriptor.m_processJobFunction = [](const AssetBuilderSDK::ProcessJobRequest& /*request*/, AssetBuilderSDK::ProcessJobResponse& response)
  4278. {
  4279. response.m_resultCode = AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success;
  4280. };
  4281. MockApplicationManager::BuilderFilePatternMatcherAndBuilderDesc builderFilePatternMatcher;
  4282. builderFilePatternMatcher.m_builderDesc = builderDescriptor;
  4283. builderFilePatternMatcher.m_internalBuilderName = builderDescriptor.m_name;
  4284. builderFilePatternMatcher.m_internalUuid = builderDescriptor.m_busId;
  4285. builderFilePatternMatcher.m_matcherBuilderPattern = AssetUtilities::BuilderFilePatternMatcher(builderDescriptor.m_patterns.back(), builderDescriptor.m_busId);
  4286. m_mockApplicationManager->m_matcherBuilderPatterns.emplace_back(builderFilePatternMatcher);
  4287. sourceFile = tempPath.absoluteFilePath("subfolder1/subfolder2/source_test." + extension);
  4288. UnitTestUtils::CreateDummyFile(sourceFile, "source");
  4289. // Tell the APM about the file:
  4290. m_isIdling = false;
  4291. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFile));
  4292. ASSERT_TRUE(BlockUntilIdle(5000));
  4293. auto filename = "product_test." + extension;
  4294. productFile.append((jobDetails[0].m_cachePath / filename.toUtf8().constData()).AsPosix().c_str());
  4295. UnitTestUtils::CreateDummyFile(productFile, "product");
  4296. // Populate ProcessJobResponse
  4297. response.m_resultCode = ProcessJobResult_Success;
  4298. JobProduct jobProduct(filename.toUtf8().constData(), AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(0));
  4299. response.m_outputProducts.push_back(jobProduct);
  4300. // Process the first job
  4301. m_isIdling = false;
  4302. m_assetProcessorManager->AssetProcessed(jobDetails[0].m_jobEntry, response);
  4303. ASSERT_TRUE(BlockUntilIdle(5000));
  4304. }
  4305. TEST_F(DuplicateProductsTest, SameSource_MultipleBuilder_DuplicateProductJobs_EmitAutoFailJob)
  4306. {
  4307. using namespace AssetProcessor;
  4308. using namespace AssetBuilderSDK;
  4309. QString productFile;
  4310. QString sourceFile;
  4311. AZStd::vector<JobDetails> jobDetails;
  4312. ProcessJobResponse response;
  4313. SetupDuplicateProductsTest(sourceFile, m_assetRootDir, productFile, jobDetails, response, false, "txt");
  4314. // ----------------------------- TEST BEGINS HERE -----------------------------
  4315. // We will process another job with the same source file outputting the same product
  4316. JobDetails jobDetail = jobDetails[1];
  4317. jobDetails.clear();
  4318. m_isIdling = false;
  4319. m_assetProcessorManager->AssetProcessed(jobDetail.m_jobEntry, response);
  4320. ASSERT_TRUE(BlockUntilIdle(5000));
  4321. EXPECT_EQ(jobDetails.size(), 1);
  4322. EXPECT_TRUE(jobDetails.back().m_jobParam.find(AZ_CRC_CE(AutoFailReasonKey)) != jobDetails.back().m_jobParam.end());
  4323. }
  4324. TEST_F(DuplicateProductsTest, SameSource_SameBuilder_DuplicateProductJobs_EmitAutoFailJob)
  4325. {
  4326. using namespace AssetProcessor;
  4327. using namespace AssetBuilderSDK;
  4328. QString productFile;
  4329. QString sourceFile;
  4330. AZStd::vector<JobDetails> jobDetails;
  4331. ProcessJobResponse response;
  4332. SetupDuplicateProductsTest(sourceFile, m_assetRootDir, productFile, jobDetails, response, true, "png");
  4333. // ----------------------------- TEST BEGINS HERE -----------------------------
  4334. // We will process another job with the same source file outputting the same product
  4335. JobDetails jobDetail = jobDetails[1];
  4336. jobDetails.clear();
  4337. m_isIdling = false;
  4338. m_assetProcessorManager->AssetProcessed(jobDetail.m_jobEntry, response);
  4339. ASSERT_TRUE(BlockUntilIdle(5000));
  4340. EXPECT_EQ(jobDetails.size(), 1);
  4341. EXPECT_TRUE(jobDetails.back().m_jobParam.find(AZ_CRC_CE(AutoFailReasonKey)) != jobDetails.back().m_jobParam.end());
  4342. }
  4343. TEST_F(DuplicateProductsTest, SameSource_MultipleBuilder_NoDuplicateProductJob_NoWarning)
  4344. {
  4345. using namespace AssetProcessor;
  4346. using namespace AssetBuilderSDK;
  4347. QString sourceFile;
  4348. QString productFile;
  4349. // Capture the job details as the APM inspects the file.
  4350. AZStd::vector<JobDetails> jobDetails;
  4351. ProcessJobResponse response;
  4352. SetupDuplicateProductsTest(sourceFile, m_assetRootDir, productFile, jobDetails, response, false, "txt");
  4353. // ----------------------------- TEST BEGINS HERE -----------------------------
  4354. // We will process another job with the same source file outputting a different product file
  4355. auto filename = "product_test1.txt";
  4356. productFile = (jobDetails[0].m_cachePath / filename).AsPosix().c_str();
  4357. UnitTestUtils::CreateDummyFile(productFile, "product");
  4358. JobProduct newJobProduct((jobDetails[0].m_relativePath / filename).c_str(), AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(0));
  4359. response.m_outputProducts.clear();
  4360. response.m_outputProducts.push_back(newJobProduct);
  4361. JobDetails jobDetail = jobDetails[1];
  4362. jobDetails.clear();
  4363. m_isIdling = false;
  4364. m_assetProcessorManager->AssetProcessed(jobDetail.m_jobEntry, response);
  4365. ASSERT_TRUE(BlockUntilIdle(5000));
  4366. EXPECT_EQ(jobDetails.size(), 0);
  4367. }
  4368. void JobDependencyTest::SetUp()
  4369. {
  4370. using namespace AzToolsFramework::AssetDatabase;
  4371. AssetProcessorManagerTest::SetUp();
  4372. m_data = AZStd::make_unique<StaticData>();
  4373. m_data->m_builderUuid = AZ::Uuid("{DE55BCCF-4D40-40FA-AB46-86C2946FBA54}");
  4374. // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
  4375. m_mockApplicationManager->BusDisconnect();
  4376. m_data->m_mockBuilderInfoHandler.CreateBuilderDescInfoRef("test builder", m_data->m_builderUuid.ToFixedString().c_str(), { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }, m_data->m_assetBuilderConfig);
  4377. m_data->m_mockBuilderInfoHandler.BusConnect();
  4378. QString watchFolderPath = m_assetRootDir.absoluteFilePath("subfolder1");
  4379. const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
  4380. // Create a dummy file and put entries in the db to simulate a previous successful AP run for this file (source, job, and product entries)
  4381. QString absPath(QDir(watchFolderPath).absoluteFilePath("a.txt"));
  4382. UnitTestUtils::CreateDummyFile(absPath);
  4383. SourceDatabaseEntry sourceEntry(scanFolder->ScanFolderID(), "a.txt", AZ::Uuid::CreateRandom(), "abcdefg");
  4384. m_assetProcessorManager->m_stateData->SetSource(sourceEntry);
  4385. JobDatabaseEntry jobEntry(sourceEntry.m_sourceID, "Mock Job", 123456, "pc", m_data->m_builderUuid, AzToolsFramework::AssetSystem::JobStatus::Completed, 1);
  4386. m_assetProcessorManager->m_stateData->SetJob(jobEntry);
  4387. ProductDatabaseEntry productEntry(jobEntry.m_jobID, 0, "a.output", AZ::Data::AssetType::CreateNull());
  4388. m_assetProcessorManager->m_stateData->SetProduct(productEntry);
  4389. // Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM
  4390. m_assetProcessorManager = nullptr; // Destroy the existing instance first so we can finish cleanup before creating a new instance
  4391. m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get()));
  4392. m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState)
  4393. {
  4394. m_isIdling = newState;
  4395. });
  4396. }
  4397. void JobDependencyTest::TearDown()
  4398. {
  4399. m_data = nullptr;
  4400. AssetProcessorManagerTest::TearDown();
  4401. }
  4402. TEST_F(JobDependencyTest, JobDependency_ThatWasPreviouslyRun_IsFound)
  4403. {
  4404. AZStd::vector<JobDetails> capturedDetails;
  4405. capturedDetails.clear();
  4406. m_data->m_assetBuilderConfig.m_jobDependencyFilePath = "a.txt";
  4407. CaptureJobs(capturedDetails, "subfolder1/b.txt");
  4408. ASSERT_EQ(capturedDetails.size(), 1);
  4409. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), 1);
  4410. ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_builderUuidList.size(), 1);
  4411. }
  4412. TEST_F(JobDependencyTest, JobDependency_ThatWasJustRun_IsFound)
  4413. {
  4414. AZStd::vector<JobDetails> capturedDetails;
  4415. CaptureJobs(capturedDetails, "subfolder1/c.txt");
  4416. capturedDetails.clear();
  4417. m_data->m_assetBuilderConfig.m_jobDependencyFilePath = "c.txt";
  4418. CaptureJobs(capturedDetails, "subfolder1/b.txt");
  4419. ASSERT_EQ(capturedDetails.size(), 1);
  4420. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), 1);
  4421. ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_builderUuidList.size(), 1);
  4422. }
  4423. TEST_F(JobDependencyTest, JobDependency_ThatHasNotRun_IsNotFound)
  4424. {
  4425. AZStd::vector<JobDetails> capturedDetails;
  4426. capturedDetails.clear();
  4427. m_data->m_assetBuilderConfig.m_jobDependencyFilePath = "c.txt";
  4428. CaptureJobs(capturedDetails, "subfolder1/b.txt");
  4429. ASSERT_EQ(capturedDetails.size(), 1);
  4430. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), 1);
  4431. ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_builderUuidList.size(), 0);
  4432. }
  4433. void ChainJobDependencyTest::SetUp()
  4434. {
  4435. using namespace AzToolsFramework::AssetDatabase;
  4436. AssetProcessorManagerTest::SetUp();
  4437. m_data = AZStd::make_unique<StaticData>();
  4438. m_data->m_rcController.reset(new RCController(/*minJobs*/1, /*maxJobs*/1));
  4439. m_data->m_rcController->SetDispatchPaused(false);
  4440. // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own
  4441. m_mockApplicationManager->BusDisconnect();
  4442. for (int i = 0; i < ChainLength; ++i)
  4443. {
  4444. QString jobDependencyPath;
  4445. if (i > 0)
  4446. {
  4447. jobDependencyPath = QString("%1.txt").arg(i - 1);
  4448. }
  4449. m_data->m_mockBuilderInfoHandler.CreateBuilderDesc(QString("test builder %1").arg(i), AZ::Uuid::CreateRandom().ToFixedString().c_str(), { AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*%d.txt", i), AssetBuilderSDK::AssetBuilderPattern::Wildcard) },
  4450. UnitTests::MockMultiBuilderInfoHandler::AssetBuilderExtraInfo{ "", "", jobDependencyPath, "", {} });
  4451. }
  4452. m_data->m_mockBuilderInfoHandler.BusConnect();
  4453. }
  4454. void ChainJobDependencyTest::TearDown()
  4455. {
  4456. m_data = nullptr;
  4457. AssetProcessorManagerTest::TearDown();
  4458. }
  4459. TEST_F(ChainJobDependencyTest, ChainDependency_EndCaseHasNoDependency)
  4460. {
  4461. AZStd::vector<JobDetails> capturedDetails;
  4462. CaptureJobs(capturedDetails, AZStd::string::format("subfolder1/%d.txt", 0).c_str());
  4463. ASSERT_EQ(capturedDetails.size(), 1);
  4464. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), 0);
  4465. }
  4466. TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
  4467. {
  4468. AZStd::vector<JobDetails> capturedDetails;
  4469. // Run through the dependencies in forward order so everything gets added to the database
  4470. for (int i = 0; i < ChainLength; ++i)
  4471. {
  4472. CaptureJobs(capturedDetails, AZStd::string::format("subfolder1/%d.txt", i).c_str());
  4473. ASSERT_EQ(capturedDetails.size(), 1);
  4474. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), i > 0 ? 1 : 0);
  4475. capturedDetails.clear();
  4476. }
  4477. QDir tempPath(m_assetRootDir.path());
  4478. // Run through the dependencies in reverse order
  4479. // Each one should trigger a job for every file in front of it
  4480. // Ex: 3 triggers -> 2 -> 1 -> 0
  4481. for (int i = ChainLength - 1; i >= 0; --i)
  4482. {
  4483. CaptureJobs(capturedDetails, AZStd::string::format("subfolder1/%d.txt", i).c_str());
  4484. ASSERT_EQ(capturedDetails.size(), ChainLength - i);
  4485. ASSERT_EQ(capturedDetails[0].m_jobDependencyList.size(), i > 0 ? 1 : 0);
  4486. if (i > 0)
  4487. {
  4488. QString absPath(tempPath.absoluteFilePath(AZStd::string::format("subfolder1/%d.txt", i - 1).c_str()));
  4489. ASSERT_EQ(capturedDetails[0].m_jobDependencyList[0].m_jobDependency.m_sourceFile.m_sourceFileDependencyPath, absPath.toUtf8().constData());
  4490. capturedDetails.clear();
  4491. }
  4492. }
  4493. // Wait for the file compiled event and trigger OnAddedToCatalog with a delay, this is what causes rccontroller to process out of order
  4494. AZStd::vector<JobEntry> finishedJobs;
  4495. QObject::connect(m_data->m_rcController.get(), &RCController::FileCompiled, [this, &finishedJobs](JobEntry entry, [[maybe_unused]] AssetBuilderSDK::ProcessJobResponse response)
  4496. {
  4497. finishedJobs.push_back(entry);
  4498. QTimer::singleShot(20, m_data->m_rcController.get(), [this, entry]()
  4499. {
  4500. QMetaObject::invokeMethod(m_data->m_rcController.get(), "OnAddedToCatalog", Qt::QueuedConnection, Q_ARG(JobEntry, entry));
  4501. });
  4502. });
  4503. // Submit all the jobs to rccontroller
  4504. for (const JobDetails& job : capturedDetails)
  4505. {
  4506. m_data->m_rcController->JobSubmitted(job);
  4507. }
  4508. QElapsedTimer timer;
  4509. timer.start();
  4510. // Wait for all the jobs to finish, up to 5 seconds
  4511. do
  4512. {
  4513. QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
  4514. } while (finishedJobs.size() < capturedDetails.size() && timer.elapsed() < 5000);
  4515. ASSERT_EQ(finishedJobs.size(), capturedDetails.size());
  4516. // Test that the jobs completed in the correct order (captureDetails has the correct ordering)
  4517. for(int i = 0; i < capturedDetails.size(); ++i)
  4518. {
  4519. ASSERT_EQ(capturedDetails[i].m_jobEntry.m_sourceAssetReference, finishedJobs[i].m_sourceAssetReference);
  4520. }
  4521. }
  4522. void DuplicateProcessTest::SetUp()
  4523. {
  4524. AssetProcessorManagerTest::SetUp();
  4525. m_sharedConnection = m_assetProcessorManager->m_stateData.get();
  4526. ASSERT_TRUE(m_sharedConnection);
  4527. }
  4528. void MetadataFileTest::SetUp()
  4529. {
  4530. AssetProcessorManagerTest::SetUp();
  4531. m_config->AddMetaDataType("foo", "txt");
  4532. }
  4533. TEST_F(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase)
  4534. {
  4535. using namespace AzToolsFramework::AssetSystem;
  4536. using namespace AssetProcessor;
  4537. QString relFileName("Dummy.TXT");
  4538. QString absPath(m_assetRootDir.absoluteFilePath("subfolder1/Dummy.TXT"));
  4539. QString watchFolder = m_assetRootDir.absoluteFilePath("subfolder1");
  4540. UnitTestUtils::CreateDummyFile(absPath, "dummy");
  4541. JobEntry entry;
  4542. entry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(watchFolder,relFileName);
  4543. entry.m_jobKey = "txt";
  4544. entry.m_platformInfo = { "pc", {"host", "renderer", "desktop"} };
  4545. entry.m_jobRunKey = 1;
  4546. const char* filename = "outputfile.TXT";
  4547. QString productPath(m_normalizedCacheRootDir.absoluteFilePath(filename));
  4548. UnitTestUtils::CreateDummyFile(productPath);
  4549. AssetBuilderSDK::ProcessJobResponse jobResponse;
  4550. jobResponse.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  4551. jobResponse.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("outputfile.TXT"));
  4552. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, entry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, jobResponse));
  4553. ASSERT_TRUE(BlockUntilIdle(5000));
  4554. // Creating a metadata file for the source assets
  4555. // APM should process the source asset if a metadafile is detected
  4556. // We are intentionally having a source file with a different file extension casing than the one specified in the metadata rule.
  4557. QString metadataFile(m_assetRootDir.absoluteFilePath("subfolder1/Dummy.foo"));
  4558. UnitTestUtils::CreateDummyFile(metadataFile, "dummy");
  4559. // Capture the job details as the APM inspects the file.
  4560. JobDetails jobDetails;
  4561. auto connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&jobDetails](JobDetails job)
  4562. {
  4563. jobDetails = job;
  4564. });
  4565. m_assetProcessorManager->AssessAddedFile(m_assetRootDir.absoluteFilePath(metadataFile));
  4566. ASSERT_TRUE(BlockUntilIdle(5000));
  4567. ASSERT_EQ(jobDetails.m_jobEntry.m_sourceAssetReference.AbsolutePath().c_str(), absPath);
  4568. }
  4569. AZStd::vector<AZStd::string> QStringListToVector(const QStringList& qstringList)
  4570. {
  4571. AZStd::vector<AZStd::string> azVector;
  4572. // Convert to a vector of AZStd::strings because GTest handles this type better when displaying errors
  4573. for (const QString& resolvedPath : qstringList)
  4574. {
  4575. azVector.emplace_back(resolvedPath.toUtf8().constData());
  4576. }
  4577. return azVector;
  4578. }
  4579. bool WildcardSourceDependencyTest::Test(
  4580. const AZStd::string& dependencyPath, AZStd::vector<AZStd::string>& resolvedPaths)
  4581. {
  4582. [[maybe_unused]] QString resolvedName;
  4583. QStringList stringlistPaths;
  4584. AssetBuilderSDK::SourceFileDependency dependency(dependencyPath, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards);
  4585. bool result = m_assetProcessorManager->ResolveSourceFileDependencyPath(dependency, resolvedName, stringlistPaths);
  4586. resolvedPaths = QStringListToVector(stringlistPaths);
  4587. return result;
  4588. }
  4589. AZStd::vector<AZStd::string> WildcardSourceDependencyTest::FileAddedTest(const QString& path)
  4590. {
  4591. auto result = m_assetProcessorManager->GetSourceFilesWhichDependOnSourceFile(path, {});
  4592. return QStringListToVector(result);
  4593. }
  4594. void WildcardSourceDependencyTest::SetUp()
  4595. {
  4596. using namespace AzToolsFramework::AssetDatabase;
  4597. AssetProcessorManagerTest::SetUp();
  4598. // Add a non-recursive scan folder. Only files directly inside of this folder should be picked up, subfolders are ignored
  4599. m_config->AddScanFolder(ScanFolderInfo(m_assetRootDir.filePath("no_recurse"), "no_recurse",
  4600. "no_recurse", false, false, m_config->GetEnabledPlatforms(), 1));
  4601. {
  4602. ExcludeAssetRecognizer excludeFolder;
  4603. excludeFolder.m_name = "Exclude ignored Folder";
  4604. excludeFolder.m_patternMatcher =
  4605. AssetBuilderSDK::FilePatternMatcher(R"REGEX(^(.*\/)?ignored(\/.*)?$)REGEX", AssetBuilderSDK::AssetBuilderPattern::Regex);
  4606. m_config->AddExcludeRecognizer(excludeFolder);
  4607. }
  4608. {
  4609. ExcludeAssetRecognizer excludeFile;
  4610. excludeFile.m_name = "Exclude z.foo Files";
  4611. excludeFile.m_patternMatcher =
  4612. AssetBuilderSDK::FilePatternMatcher(R"REGEX(^(.*\/)?z\.foo$)REGEX", AssetBuilderSDK::AssetBuilderPattern::Regex);
  4613. m_config->AddExcludeRecognizer(excludeFile);
  4614. }
  4615. CreateSourceAndFile("subfolder1/1a.foo");
  4616. CreateSourceAndFile("subfolder1/1b.foo");
  4617. CreateSourceAndFile("subfolder2/a.foo");
  4618. CreateSourceAndFile("subfolder2/b.foo");
  4619. CreateSourceAndFile("subfolder2/folder/one/c.foo");
  4620. CreateSourceAndFile("subfolder2/folder/one/d.foo");
  4621. // Add a file that is not in a scanfolder. Should always be ignored
  4622. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("not/a/scanfolder/e.foo"));
  4623. // Add a file in the non-recursive scanfolder. Since its not directly in the scan folder, it should always be ignored
  4624. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("no_recurse/one/two/three/f.foo"));
  4625. // Add a file to an ignored folder
  4626. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder2/folder/ignored/g.foo"));
  4627. // Add an ignored file
  4628. UnitTestUtils::CreateDummyFile(m_assetRootDir.absoluteFilePath("subfolder2/folder/one/z.foo"));
  4629. // Add a file in the cache
  4630. AZStd::string projectCacheRootValue;
  4631. AZ::SettingsRegistry::Get()->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
  4632. projectCacheRootValue = AssetUtilities::NormalizeFilePath(projectCacheRootValue.c_str()).toUtf8().constData();
  4633. auto path = AZ::IO::Path(projectCacheRootValue) / "cache.foo";
  4634. UnitTestUtils::CreateDummyFile(path.c_str());
  4635. AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer dependencies;
  4636. auto aUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder2/a.foo")));
  4637. auto bUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder2/b.foo")));
  4638. auto dUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(m_assetRootDir.absoluteFilePath("subfolder2/folder/one/d.foo")));
  4639. ASSERT_TRUE(aUuid);
  4640. ASSERT_TRUE(bUuid);
  4641. ASSERT_TRUE(dUuid);
  4642. // Relative path wildcard dependency
  4643. dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry(
  4644. AZ::Uuid::CreateRandom(),
  4645. aUuid.GetValue(),
  4646. PathOrUuid("%a.foo"),
  4647. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0, ""));
  4648. // Absolute path wildcard dependency
  4649. dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry(
  4650. AZ::Uuid::CreateRandom(),
  4651. bUuid.GetValue(),
  4652. PathOrUuid(m_assetRootDir.absoluteFilePath("%b.foo").toUtf8().constData()),
  4653. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0, ""));
  4654. // Test what happens when we have 2 dependencies on the same file
  4655. dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry(
  4656. AZ::Uuid::CreateRandom(),
  4657. dUuid.GetValue(),
  4658. PathOrUuid("%c.foo"),
  4659. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0, ""));
  4660. dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry(
  4661. AZ::Uuid::CreateRandom(),
  4662. dUuid.GetValue(),
  4663. PathOrUuid(m_assetRootDir.absoluteFilePath("%c.foo").toUtf8().constData()),
  4664. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0, ""));
  4665. #ifdef AZ_PLATFORM_WINDOWS
  4666. // Test to make sure a relative wildcard dependency doesn't match an absolute path
  4667. // For example, if the input is C:/project/subfolder1/a.foo
  4668. // This should not match a wildcard of c%.foo
  4669. // Take the first character of the m_assetRootDir and append %.foo onto it for this test, which should produce something like c%.foo
  4670. // This only applies to windows because on other OSes if the dependency starts with /, then its an abs path dependency
  4671. auto test = (m_assetRootDir.absolutePath().left(1) + "%.foo");
  4672. dependencies.push_back(AzToolsFramework::AssetDatabase::SourceFileDependencyEntry(
  4673. AZ::Uuid::CreateRandom(),
  4674. dUuid.GetValue(),
  4675. PathOrUuid(test.toUtf8().constData()),
  4676. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, 0, ""));
  4677. #endif
  4678. ASSERT_TRUE(m_assetProcessorManager->m_stateData->SetSourceFileDependencies(dependencies));
  4679. }
  4680. TEST_F(WildcardSourceDependencyTest, Relative_Broad)
  4681. {
  4682. // Expect all files except for the 2 invalid ones (e and f)
  4683. AZStd::vector<AZStd::string> resolvedPaths;
  4684. ASSERT_TRUE(Test("*.foo", resolvedPaths));
  4685. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "b.foo", "folder/one/c.foo", "folder/one/d.foo", "1a.foo", "1b.foo"));
  4686. }
  4687. TEST_F(WildcardSourceDependencyTest, Relative_WithFolder)
  4688. {
  4689. // Make sure we can filter to files under a folder
  4690. AZStd::vector<AZStd::string> resolvedPaths;
  4691. ASSERT_TRUE(Test("folder/*.foo", resolvedPaths));
  4692. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("folder/one/c.foo", "folder/one/d.foo"));
  4693. }
  4694. TEST_F(WildcardSourceDependencyTest, Relative_WildcardPath)
  4695. {
  4696. // Make sure the * wildcard works even if the full filename is given
  4697. AZStd::vector<AZStd::string> resolvedPaths;
  4698. ASSERT_TRUE(Test("*a.foo", resolvedPaths));
  4699. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "1a.foo"));
  4700. }
  4701. TEST_F(WildcardSourceDependencyTest, Absolute_WithFolder)
  4702. {
  4703. // Make sure we can use absolute paths to filter to files under a folder
  4704. AZStd::vector<AZStd::string> resolvedPaths;
  4705. ASSERT_TRUE(Test(m_assetRootDir.absoluteFilePath("subfolder2/*.foo").toUtf8().constData(), resolvedPaths));
  4706. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre("a.foo", "b.foo", "folder/one/c.foo", "folder/one/d.foo"));
  4707. }
  4708. TEST_F(WildcardSourceDependencyTest, Absolute_NotInScanfolder)
  4709. {
  4710. // Files outside a scanfolder should not be returned even with an absolute path
  4711. AZStd::vector<AZStd::string> resolvedPaths;
  4712. ASSERT_TRUE(Test(m_assetRootDir.absoluteFilePath("not/a/scanfolder/*.foo").toUtf8().constData(), resolvedPaths));
  4713. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4714. }
  4715. TEST_F(WildcardSourceDependencyTest, Relative_NotInScanfolder)
  4716. {
  4717. // Files outside a scanfolder should not be returned
  4718. AZStd::vector<AZStd::string> resolvedPaths;
  4719. ASSERT_TRUE(Test("*/e.foo", resolvedPaths));
  4720. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4721. }
  4722. TEST_F(WildcardSourceDependencyTest, Relative_InNonRecursiveScanfolder)
  4723. {
  4724. // Files deep inside non-recursive scanfolders should not be returned
  4725. AZStd::vector<AZStd::string> resolvedPaths;
  4726. ASSERT_TRUE(Test("*/f.foo", resolvedPaths));
  4727. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4728. }
  4729. TEST_F(WildcardSourceDependencyTest, Absolute_InNonRecursiveScanfolder)
  4730. {
  4731. // Absolute paths to files deep inside non-recursive scanfolders should not be returned
  4732. AZStd::vector<AZStd::string> resolvedPaths;
  4733. ASSERT_TRUE(Test(m_assetRootDir.absoluteFilePath("one/two/three/*.foo").toUtf8().constData(), resolvedPaths));
  4734. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4735. }
  4736. TEST_F(WildcardSourceDependencyTest, Relative_NoWildcard)
  4737. {
  4738. // No wildcard results in a failure
  4739. AZStd::vector<AZStd::string> resolvedPaths;
  4740. ASSERT_FALSE(Test("subfolder1/1a.foo", resolvedPaths));
  4741. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4742. }
  4743. TEST_F(WildcardSourceDependencyTest, Absolute_NoWildcard)
  4744. {
  4745. // No wildcard results in a failure
  4746. AZStd::vector<AZStd::string> resolvedPaths;
  4747. ASSERT_FALSE(Test(m_assetRootDir.absoluteFilePath("subfolder1/1a.foo").toUtf8().constData(), resolvedPaths));
  4748. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4749. }
  4750. TEST_F(WildcardSourceDependencyTest, Relative_IgnoredFolder)
  4751. {
  4752. AZStd::vector<AZStd::string> resolvedPaths;
  4753. ASSERT_TRUE(Test("*g.foo", resolvedPaths));
  4754. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4755. }
  4756. TEST_F(WildcardSourceDependencyTest, Absolute_IgnoredFolder)
  4757. {
  4758. AZStd::vector<AZStd::string> resolvedPaths;
  4759. ASSERT_TRUE(Test(m_assetRootDir.absoluteFilePath("*g.foo").toUtf8().constData(), resolvedPaths));
  4760. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4761. }
  4762. TEST_F(WildcardSourceDependencyTest, Relative_IgnoredFile)
  4763. {
  4764. AZStd::vector<AZStd::string> resolvedPaths;
  4765. ASSERT_TRUE(Test("*z.foo", resolvedPaths));
  4766. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4767. }
  4768. TEST_F(WildcardSourceDependencyTest, Absolute_IgnoredFile)
  4769. {
  4770. AZStd::vector<AZStd::string> resolvedPaths;
  4771. ASSERT_TRUE(Test(m_assetRootDir.absoluteFilePath("*z.foo").toUtf8().constData(), resolvedPaths));
  4772. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4773. }
  4774. TEST_F(WildcardSourceDependencyTest, Relative_CacheFolder)
  4775. {
  4776. AZStd::vector<AZStd::string> resolvedPaths;
  4777. ASSERT_TRUE(Test("*cache.foo", resolvedPaths));
  4778. ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
  4779. }
  4780. TEST_F(WildcardSourceDependencyTest, FilesAddedAfterInitialCache)
  4781. {
  4782. AZStd::vector<AZStd::string> resolvedPaths;
  4783. auto excludedFolderCacheInterface = AZ::Interface<ExcludedFolderCacheInterface>::Get();
  4784. ASSERT_TRUE(excludedFolderCacheInterface);
  4785. {
  4786. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4787. ASSERT_EQ(excludedFolders.size(), 2);
  4788. }
  4789. // Add a file to a new ignored folder
  4790. QString newFilePath = m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored/three/new.foo");
  4791. UnitTestUtils::CreateDummyFile(newFilePath);
  4792. excludedFolderCacheInterface->FileAdded(newFilePath);
  4793. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4794. ASSERT_EQ(excludedFolders.size(), 3);
  4795. ASSERT_THAT(excludedFolders, ::testing::Contains(AZStd::string(m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored").toUtf8().constData())));
  4796. }
  4797. TEST_F(WildcardSourceDependencyTest, FilesRemovedAfterInitialCache)
  4798. {
  4799. AZStd::vector<AZStd::string> resolvedPaths;
  4800. // Add a file to a new ignored folder
  4801. QString newFilePath = m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored/three/new.foo");
  4802. UnitTestUtils::CreateDummyFile(newFilePath);
  4803. auto excludedFolderCacheInterface = AZ::Interface<ExcludedFolderCacheInterface>::Get();
  4804. ASSERT_TRUE(excludedFolderCacheInterface);
  4805. {
  4806. m_errorAbsorber->Clear();
  4807. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4808. m_errorAbsorber->ExpectWarnings(1); // because we didn't precache, we'd expect a warning here, about performance.
  4809. ASSERT_EQ(excludedFolders.size(), 3);
  4810. }
  4811. m_fileStateCache->SignalDeleteEvent(m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored"));
  4812. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4813. ASSERT_EQ(excludedFolders.size(), 2);
  4814. }
  4815. // same as above test but actually runs a file scanner over the root dir and ensures it still functions
  4816. TEST_F(WildcardSourceDependencyTest, FilesRemovedAfterInitialCache_WithPrecache)
  4817. {
  4818. // Add a file to a new ignored folder
  4819. QString newFilePath = m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored/three/new.foo");
  4820. UnitTestUtils::CreateDummyFile(newFilePath);
  4821. {
  4822. // warm up the cache.
  4823. AssetScannerWorker worker(m_config.get());
  4824. bool foundExcludes = false;
  4825. QObject::connect(
  4826. &worker,
  4827. &AssetScannerWorker::ExcludedFound,
  4828. m_assetProcessorManager.get(),
  4829. [&](QSet<AssetFileInfo> excluded)
  4830. {
  4831. foundExcludes = true;
  4832. m_assetProcessorManager->RecordExcludesFromScanner(excluded);
  4833. });
  4834. worker.StartScan();
  4835. QCoreApplication::processEvents();
  4836. ASSERT_TRUE(foundExcludes);
  4837. }
  4838. auto excludedFolderCacheInterface = AZ::Interface<ExcludedFolderCacheInterface>::Get();
  4839. ASSERT_TRUE(excludedFolderCacheInterface);
  4840. {
  4841. m_errorAbsorber->Clear();
  4842. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4843. m_errorAbsorber->ExpectWarnings(0); // we precached, so there should not be a warning.
  4844. ASSERT_EQ(excludedFolders.size(), 3);
  4845. }
  4846. m_fileStateCache->SignalDeleteEvent(m_assetRootDir.absoluteFilePath("subfolder2/folder/two/ignored"));
  4847. const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
  4848. ASSERT_EQ(excludedFolders.size(), 2);
  4849. }
  4850. TEST_F(WildcardSourceDependencyTest, NewFile_MatchesSavedRelativeDependency)
  4851. {
  4852. auto matches = FileAddedTest(m_assetRootDir.absoluteFilePath("subfolder1/1a.foo"));
  4853. ASSERT_THAT(matches, ::testing::UnorderedElementsAre(m_assetRootDir.absoluteFilePath("subfolder2/a.foo").toUtf8().constData()));
  4854. }
  4855. TEST_F(WildcardSourceDependencyTest, NewFile_MatchesSavedAbsoluteDependency)
  4856. {
  4857. auto matches = FileAddedTest(m_assetRootDir.absoluteFilePath("subfolder1/1b.foo"));
  4858. ASSERT_THAT(matches, ::testing::UnorderedElementsAre(m_assetRootDir.absoluteFilePath("subfolder2/b.foo").toUtf8().constData()));
  4859. }
  4860. TEST_F(WildcardSourceDependencyTest, NewFile_MatchesDuplicatedDependenciesOnce)
  4861. {
  4862. auto matches = FileAddedTest(m_assetRootDir.absoluteFilePath("subfolder2/folder/one/c.foo"));
  4863. ASSERT_THAT(matches, ::testing::UnorderedElementsAre(m_assetRootDir.absoluteFilePath("subfolder2/folder/one/d.foo").toUtf8().constData()));
  4864. }