@@ -1764,3 +1764,175 @@ func TestExecutorAutostartSkipsWhenNoProvisionersAvailable(t *testing.T) {
17641764
17651765 assert .Len (t , stats .Transitions , 1 , "should create builds when provisioners are available" )
17661766}
1767+
1768+ func TestExecutorTaskWorkspace (t * testing.T ) {
1769+ t .Parallel ()
1770+
1771+ createTaskTemplate := func (t * testing.T , client * codersdk.Client , orgID uuid.UUID , ctx context.Context , defaultTTL time.Duration ) codersdk.Template {
1772+ t .Helper ()
1773+
1774+ taskAppID := uuid .New ()
1775+ version := coderdtest .CreateTemplateVersion (t , client , orgID , & echo.Responses {
1776+ Parse : echo .ParseComplete ,
1777+ ProvisionPlan : []* proto.Response {
1778+ {
1779+ Type : & proto.Response_Plan {
1780+ Plan : & proto.PlanComplete {HasAiTasks : true },
1781+ },
1782+ },
1783+ },
1784+ ProvisionApply : []* proto.Response {
1785+ {
1786+ Type : & proto.Response_Apply {
1787+ Apply : & proto.ApplyComplete {
1788+ Resources : []* proto.Resource {
1789+ {
1790+ Agents : []* proto.Agent {
1791+ {
1792+ Id : uuid .NewString (),
1793+ Name : "dev" ,
1794+ Auth : & proto.Agent_Token {
1795+ Token : uuid .NewString (),
1796+ },
1797+ Apps : []* proto.App {
1798+ {
1799+ Id : taskAppID .String (),
1800+ Slug : "task-app" ,
1801+ },
1802+ },
1803+ },
1804+ },
1805+ },
1806+ },
1807+ AiTasks : []* proto.AITask {
1808+ {
1809+ AppId : taskAppID .String (),
1810+ },
1811+ },
1812+ },
1813+ },
1814+ },
1815+ },
1816+ })
1817+ coderdtest .AwaitTemplateVersionJobCompleted (t , client , version .ID )
1818+ template := coderdtest .CreateTemplate (t , client , orgID , version .ID )
1819+
1820+ if defaultTTL > 0 {
1821+ _ , err := client .UpdateTemplateMeta (ctx , template .ID , codersdk.UpdateTemplateMeta {
1822+ DefaultTTLMillis : defaultTTL .Milliseconds (),
1823+ })
1824+ require .NoError (t , err )
1825+ }
1826+
1827+ return template
1828+ }
1829+
1830+ createTaskWorkspace := func (t * testing.T , client * codersdk.Client , template codersdk.Template , ctx context.Context , input string ) codersdk.Workspace {
1831+ t .Helper ()
1832+
1833+ exp := codersdk .NewExperimentalClient (client )
1834+ task , err := exp .CreateTask (ctx , "me" , codersdk.CreateTaskRequest {
1835+ TemplateVersionID : template .ActiveVersionID ,
1836+ Input : input ,
1837+ })
1838+ require .NoError (t , err )
1839+ require .True (t , task .WorkspaceID .Valid , "task should have a workspace" )
1840+
1841+ workspace , err := client .Workspace (ctx , task .WorkspaceID .UUID )
1842+ require .NoError (t , err )
1843+ coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , workspace .LatestBuild .ID )
1844+
1845+ return workspace
1846+ }
1847+
1848+ t .Run ("Autostart" , func (t * testing.T ) {
1849+ t .Parallel ()
1850+
1851+ var (
1852+ ctx = testutil .Context (t , testutil .WaitShort )
1853+ sched = mustSchedule (t , "CRON_TZ=UTC 0 * * * *" )
1854+ tickCh = make (chan time.Time )
1855+ statsCh = make (chan autobuild.Stats )
1856+ client , db = coderdtest .NewWithDatabase (t , & coderdtest.Options {
1857+ AutobuildTicker : tickCh ,
1858+ IncludeProvisionerDaemon : true ,
1859+ AutobuildStats : statsCh ,
1860+ })
1861+ admin = coderdtest .CreateFirstUser (t , client )
1862+ )
1863+
1864+ // Given: A task workspace
1865+ template := createTaskTemplate (t , client , admin .OrganizationID , ctx , 0 )
1866+ workspace := createTaskWorkspace (t , client , template , ctx , "test task for autostart" )
1867+
1868+ // Given: The task workspace has an autostart schedule
1869+ err := client .UpdateWorkspaceAutostart (ctx , workspace .ID , codersdk.UpdateWorkspaceAutostartRequest {
1870+ Schedule : ptr .Ref (sched .String ()),
1871+ })
1872+ require .NoError (t , err )
1873+
1874+ // Given: That the workspace is in a stopped state.
1875+ workspace = coderdtest .MustTransitionWorkspace (t , client , workspace .ID , codersdk .WorkspaceTransitionStart , codersdk .WorkspaceTransitionStop )
1876+
1877+ p , err := coderdtest .GetProvisionerForTags (db , time .Now (), workspace .OrganizationID , map [string ]string {})
1878+ require .NoError (t , err )
1879+
1880+ // When: the autobuild executor ticks after the scheduled time
1881+ go func () {
1882+ tickTime := sched .Next (workspace .LatestBuild .CreatedAt )
1883+ coderdtest .UpdateProvisionerLastSeenAt (t , db , p .ID , tickTime )
1884+ tickCh <- tickTime
1885+ close (tickCh )
1886+ }()
1887+
1888+ // Then: We expect to see a start transition
1889+ stats := <- statsCh
1890+ require .Len (t , stats .Transitions , 1 , "lifecycle executor should transition the task workspace" )
1891+ assert .Contains (t , stats .Transitions , workspace .ID , "task workspace should be in transitions" )
1892+ assert .Equal (t , database .WorkspaceTransitionStart , stats .Transitions [workspace .ID ], "should autostart the workspace" )
1893+ require .Empty (t , stats .Errors , "should have no errors when managing task workspaces" )
1894+ })
1895+
1896+ t .Run ("Autostop" , func (t * testing.T ) {
1897+ t .Parallel ()
1898+
1899+ var (
1900+ ctx = testutil .Context (t , testutil .WaitShort )
1901+ tickCh = make (chan time.Time )
1902+ statsCh = make (chan autobuild.Stats )
1903+ client , db = coderdtest .NewWithDatabase (t , & coderdtest.Options {
1904+ AutobuildTicker : tickCh ,
1905+ IncludeProvisionerDaemon : true ,
1906+ AutobuildStats : statsCh ,
1907+ })
1908+ admin = coderdtest .CreateFirstUser (t , client )
1909+ )
1910+
1911+ // Given: A task workspace with an 8 hour deadline
1912+ template := createTaskTemplate (t , client , admin .OrganizationID , ctx , 8 * time .Hour )
1913+ workspace := createTaskWorkspace (t , client , template , ctx , "test task for autostop" )
1914+
1915+ // Given: The workspace is currently running
1916+ workspace = coderdtest .MustWorkspace (t , client , workspace .ID )
1917+ require .Equal (t , codersdk .WorkspaceTransitionStart , workspace .LatestBuild .Transition )
1918+ require .NotZero (t , workspace .LatestBuild .Deadline , "workspace should have a deadline for autostop" )
1919+
1920+ p , err := coderdtest .GetProvisionerForTags (db , time .Now (), workspace .OrganizationID , map [string ]string {})
1921+ require .NoError (t , err )
1922+
1923+ // When: the autobuild executor ticks after the deadline
1924+ go func () {
1925+ tickTime := workspace .LatestBuild .Deadline .Time .Add (time .Minute )
1926+ coderdtest .UpdateProvisionerLastSeenAt (t , db , p .ID , tickTime )
1927+ tickCh <- tickTime
1928+ close (tickCh )
1929+ }()
1930+
1931+ // Then: We expect to see a stop transition
1932+ stats := <- statsCh
1933+ require .Len (t , stats .Transitions , 1 , "lifecycle executor should transition the task workspace" )
1934+ assert .Contains (t , stats .Transitions , workspace .ID , "task workspace should be in transitions" )
1935+ assert .Equal (t , database .WorkspaceTransitionStop , stats .Transitions [workspace .ID ], "should autostop the workspace" )
1936+ require .Empty (t , stats .Errors , "should have no errors when managing task workspaces" )
1937+ })
1938+ }
0 commit comments