Skip to content

Commit e674b01

Browse files
authored
Merge pull request #26 from Quafadas/deflake
Deflake test suite
2 parents 9321524 + 5c2cccc commit e674b01

File tree

8 files changed

+372
-366
lines changed

8 files changed

+372
-366
lines changed

project/src/htmlGen.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def lessStyle(withStyles: Boolean): Seq[Modifier] =
2020
)
2121
else Seq.empty
2222

23-
val refreshScript = script(raw("""const sse = new EventSource('/api/v1/sse');
23+
val refreshScript = script(raw("""const sse = new EventSource('/refresh/v1/sse');
2424
sse.addEventListener('message', (e) => {
2525
const msg = JSON.parse(e.data)
2626

project/src/live.server.scala

Lines changed: 149 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.http4s.*
55
import org.http4s.HttpApp
66
import org.http4s.ember.client.EmberClientBuilder
77
import org.http4s.ember.server.EmberServerBuilder
8+
import org.http4s.server.Server
89

910
import com.comcast.ip4s.Port
1011
import com.comcast.ip4s.host
@@ -156,6 +157,13 @@ object LiveServer extends IOApp:
156157
)
157158
.orNone
158159

160+
val preventBrowserOpenOpt = Opts
161+
.flag(
162+
"prevent-browser-open",
163+
"prevent the browser from opening on server start"
164+
)
165+
.orFalse
166+
159167
val millModuleNameOpt: Opts[Option[String]] = Opts
160168
.option[String](
161169
"mill-module-name",
@@ -167,128 +175,144 @@ object LiveServer extends IOApp:
167175
}
168176
.orNone
169177

170-
def main: Opts[IO[ExitCode]] =
171-
(
172-
baseDirOpt,
173-
outDirOpt,
174-
portOpt,
175-
proxyPortTargetOpt,
176-
proxyPathMatchPrefixOpt,
177-
clientRoutingPrefixOpt,
178-
logLevelOpt,
179-
buildToolOpt,
180-
openBrowserAtOpt,
181-
extraBuildArgsOpt,
182-
millModuleNameOpt,
183-
stylesDirOpt,
184-
indexHtmlTemplateOpt
185-
).mapN {
186-
(
187-
baseDir,
188-
outDirOpt,
189-
port,
190-
proxyTarget,
191-
pathPrefix,
192-
clientRoutingPrefix,
193-
lvl,
194-
buildTool,
195-
openBrowserAt,
196-
extraBuildArgs,
197-
millModuleName,
198-
stylesDir,
199-
indexHtmlTemplate
200-
) =>
201-
202-
scribe
203-
.Logger
204-
.root
205-
.clearHandlers()
206-
.clearModifiers()
207-
.withHandler(minimumLevel = Some(Level.get(lvl).get))
208-
.replace()
209-
210-
val server = for
211-
_ <- logger
212-
.debug(
213-
s"baseDir: $baseDir \n outDir: $outDirOpt \n stylesDir: $stylesDir \n indexHtmlTemplate: $indexHtmlTemplate \n port: $port \n proxyTarget: $proxyTarget \n pathPrefix: $pathPrefix \n extraBuildArgs: $extraBuildArgs"
214-
)
215-
.toResource
216-
217-
fileToHashRef <- Ref[IO].of(Map.empty[String, String]).toResource
218-
refreshTopic <- Topic[IO, Unit].toResource
219-
linkingTopic <- Topic[IO, Unit].toResource
220-
client <- EmberClientBuilder.default[IO].build
221-
baseDirPath <- baseDir.fold(Files[IO].currentWorkingDirectory.toResource)(toDirectoryPath)
222-
outDirPath <- outDirOpt.fold(Files[IO].tempDirectory)(toDirectoryPath)
223-
outDirString = outDirPath.show
224-
indexHtmlTemplatePath <- indexHtmlTemplate.traverse(toDirectoryPath)
225-
stylesDirPath <- stylesDir.traverse(toDirectoryPath)
226-
227-
indexOpts <- (indexHtmlTemplatePath, stylesDirPath) match
228-
case (Some(html), None) =>
229-
val indexHtmlFile = html / "index.html"
230-
println(indexHtmlFile)
231-
(for
232-
indexHtmlExists <- Files[IO].exists(indexHtmlFile)
233-
_ <- IO.raiseUnless(indexHtmlExists)(CliValidationError(s"index.html doesn't exist in $html"))
234-
indexHtmlIsAFile <- Files[IO].isRegularFile(indexHtmlFile)
235-
_ <- IO.raiseUnless(indexHtmlIsAFile)(CliValidationError(s"$indexHtmlFile is not a file"))
236-
yield IndexHtmlConfig.IndexHtmlPath(html).some).toResource
237-
case (None, Some(styles)) =>
238-
val indexLessFile = styles / "index.less"
239-
(for
240-
indexLessExists <- Files[IO].exists(indexLessFile)
241-
_ <- IO.raiseUnless(indexLessExists)(CliValidationError(s"index.html doesn't exist in $styles"))
242-
indexLessIsAFile <- Files[IO].isRegularFile(indexLessFile)
243-
_ <- IO.raiseUnless(indexLessIsAFile)(CliValidationError(s"$indexLessFile is not a file"))
244-
yield IndexHtmlConfig.StylesOnly(styles).some).toResource
245-
case (None, None) =>
246-
Resource.pure(Option.empty[IndexHtmlConfig])
247-
case (Some(_), Some(_)) =>
248-
Resource.raiseError[IO, Nothing, Throwable](
249-
CliValidationError("path-to-index-html and styles-dir can't be defined at the same time")
250-
)
251-
252-
proxyConf2 <- proxyConf(proxyTarget, pathPrefix)
253-
proxyRoutes: HttpRoutes[IO] = makeProxyRoutes(client, proxyConf2)(logger)
254-
255-
_ <- buildRunner(
256-
buildTool,
257-
linkingTopic,
258-
baseDirPath,
259-
outDirPath,
260-
extraBuildArgs,
261-
millModuleName
262-
)(logger)
263-
264-
app <- routes(outDirString, refreshTopic, indexOpts, proxyRoutes, fileToHashRef, clientRoutingPrefix)(logger)
265-
266-
_ <- updateMapRef(outDirPath, fileToHashRef)(logger).toResource
267-
// _ <- stylesDir.fold(Resource.unit)(sd => seedMapOnStart(sd, mr))
268-
_ <- fileWatcher(outDirPath, fileToHashRef, linkingTopic, refreshTopic)(logger)
269-
_ <- indexOpts.match
270-
case Some(IndexHtmlConfig.IndexHtmlPath(indexHtmlatPath)) =>
271-
staticWatcher(refreshTopic, fs2.io.file.Path(indexHtmlatPath.toString))(logger)
272-
case _ => Resource.unit[IO]
273-
274-
// _ <- stylesDir.fold(Resource.unit[IO])(sd => fileWatcher(fs2.io.file.Path(sd), mr))
275-
_ <- logger.info(s"Start dev server on http://localhost:$port").toResource
276-
server <- buildServer(app.orNotFound, port)
277-
278-
- <- openBrowser(Some(openBrowserAt), port)(logger).toResource
279-
yield server
280-
281-
server
282-
.useForever
283-
.as(ExitCode.Success)
284-
.handleErrorWith {
285-
case CliValidationError(message) =>
286-
IO.println(s"$message\n${command.showHelp}").as(ExitCode.Error)
287-
case error => IO.raiseError(error)
288-
}
289-
}
178+
case class LiveServerConfig(
179+
baseDir: Option[String],
180+
outDir: Option[String] = None,
181+
port: Port,
182+
proxyPortTarget: Option[Port] = None,
183+
proxyPathMatchPrefix: Option[String] = None,
184+
clientRoutingPrefix: Option[String] = None,
185+
logLevel: String = "info",
186+
buildTool: BuildTool = ScalaCli(),
187+
openBrowserAt: String,
188+
preventBrowserOpen: Boolean = false,
189+
extraBuildArgs: List[String] = List.empty,
190+
millModuleName: Option[String] = None,
191+
stylesDir: Option[String] = None,
192+
indexHtmlTemplate: Option[String] = None
193+
)
194+
195+
def parseOpts = (
196+
baseDirOpt,
197+
outDirOpt,
198+
portOpt,
199+
proxyPortTargetOpt,
200+
proxyPathMatchPrefixOpt,
201+
clientRoutingPrefixOpt,
202+
logLevelOpt,
203+
buildToolOpt,
204+
openBrowserAtOpt,
205+
preventBrowserOpenOpt,
206+
extraBuildArgsOpt,
207+
millModuleNameOpt,
208+
stylesDirOpt,
209+
indexHtmlTemplateOpt
210+
).mapN(LiveServerConfig.apply)
211+
212+
def main(lsc: LiveServerConfig): Resource[IO, Server] =
213+
214+
scribe
215+
.Logger
216+
.root
217+
.clearHandlers()
218+
.clearModifiers()
219+
.withHandler(minimumLevel = Some(Level.get(lsc.logLevel).get))
220+
.replace()
221+
222+
val server = for
223+
_ <- logger
224+
.debug(
225+
lsc.toString()
226+
)
227+
.toResource
228+
229+
fileToHashRef <- Ref[IO].of(Map.empty[String, String]).toResource
230+
refreshTopic <- Topic[IO, Unit].toResource
231+
linkingTopic <- Topic[IO, Unit].toResource
232+
client <- EmberClientBuilder.default[IO].build
233+
baseDirPath <- lsc.baseDir.fold(Files[IO].currentWorkingDirectory.toResource)(toDirectoryPath)
234+
outDirPath <- lsc.outDir.fold(Files[IO].tempDirectory)(toDirectoryPath)
235+
outDirString = outDirPath.show
236+
indexHtmlTemplatePath <- lsc.indexHtmlTemplate.traverse(toDirectoryPath)
237+
stylesDirPath <- lsc.stylesDir.traverse(toDirectoryPath)
238+
239+
indexOpts <- (indexHtmlTemplatePath, stylesDirPath) match
240+
case (Some(html), None) =>
241+
val indexHtmlFile = html / "index.html"
242+
println(indexHtmlFile)
243+
(for
244+
indexHtmlExists <- Files[IO].exists(indexHtmlFile)
245+
_ <- IO.raiseUnless(indexHtmlExists)(CliValidationError(s"index.html doesn't exist in $html"))
246+
indexHtmlIsAFile <- Files[IO].isRegularFile(indexHtmlFile)
247+
_ <- IO.raiseUnless(indexHtmlIsAFile)(CliValidationError(s"$indexHtmlFile is not a file"))
248+
yield IndexHtmlConfig.IndexHtmlPath(html).some).toResource
249+
case (None, Some(styles)) =>
250+
val indexLessFile = styles / "index.less"
251+
(for
252+
indexLessExists <- Files[IO].exists(indexLessFile)
253+
_ <- IO.raiseUnless(indexLessExists)(CliValidationError(s"index.html doesn't exist in $styles"))
254+
indexLessIsAFile <- Files[IO].isRegularFile(indexLessFile)
255+
_ <- IO.raiseUnless(indexLessIsAFile)(CliValidationError(s"$indexLessFile is not a file"))
256+
yield IndexHtmlConfig.StylesOnly(styles).some).toResource
257+
case (None, None) =>
258+
Resource.pure(Option.empty[IndexHtmlConfig])
259+
case (Some(_), Some(_)) =>
260+
Resource.raiseError[IO, Nothing, Throwable](
261+
CliValidationError("path-to-index-html and styles-dir can't be defined at the same time")
262+
)
263+
264+
proxyConf2 <- proxyConf(lsc.proxyPortTarget, lsc.proxyPathMatchPrefix)
265+
proxyRoutes: HttpRoutes[IO] = makeProxyRoutes(client, proxyConf2)(logger)
266+
267+
_ <- buildRunner(
268+
lsc.buildTool,
269+
linkingTopic,
270+
baseDirPath,
271+
outDirPath,
272+
lsc.extraBuildArgs,
273+
lsc.millModuleName
274+
)(logger)
275+
276+
app <- routes(outDirString, refreshTopic, indexOpts, proxyRoutes, fileToHashRef, lsc.clientRoutingPrefix)(logger)
277+
278+
_ <- updateMapRef(outDirPath, fileToHashRef)(logger).toResource
279+
// _ <- stylesDir.fold(Resource.unit)(sd => seedMapOnStart(sd, mr))
280+
_ <- fileWatcher(outDirPath, fileToHashRef, linkingTopic, refreshTopic)(logger)
281+
_ <- indexOpts.match
282+
case Some(IndexHtmlConfig.IndexHtmlPath(indexHtmlatPath)) =>
283+
staticWatcher(refreshTopic, fs2.io.file.Path(indexHtmlatPath.toString))(logger)
284+
case _ => Resource.unit[IO]
285+
286+
// _ <- stylesDir.fold(Resource.unit[IO])(sd => fileWatcher(fs2.io.file.Path(sd), mr))
287+
_ <- logger.info(s"Start dev server on http://localhost:${lsc.port}").toResource
288+
server <- buildServer(app.orNotFound, lsc.port)
289+
290+
// - <- openBrowser(Some(openBrowserAt), port)(logger).toResource
291+
yield server
292+
293+
server
294+
// .useForever
295+
// .as(ExitCode.Success)
296+
// .handleErrorWith {
297+
// case CliValidationError(message) =>
298+
// IO.println(s"$message\n${command.showHelp}").as(ExitCode.Error)
299+
// case error => IO.raiseError(error)
300+
// }
301+
290302
end main
291303

304+
def runServerHandleErrors: Opts[IO[ExitCode]] = parseOpts.map(
305+
ops =>
306+
main(ops)
307+
.useForever
308+
.as(ExitCode.Success)
309+
.handleErrorWith {
310+
case CliValidationError(message) =>
311+
IO.println(s"${command.showHelp} \n $message \n see help above").as(ExitCode.Error)
312+
case error => IO.raiseError(error)
313+
}
314+
)
315+
292316
val command =
293317
val versionFlag = Opts.flag(
294318
long = "version",
@@ -298,7 +322,11 @@ object LiveServer extends IOApp:
298322
)
299323

300324
val version = "0.0.1"
301-
val finalOpts = versionFlag.as(IO.println(version).as(ExitCode.Success)).orElse(main)
325+
val finalOpts = versionFlag
326+
.as(IO.println(version).as(ExitCode.Success))
327+
.orElse(
328+
runServerHandleErrors
329+
)
302330
Command(name = "LiveServer", header = "Scala JS live server", helpFlag = true)(finalOpts)
303331
end command
304332

project/src/middleware/static.path.middleware.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ inline def cachedFileResponse(epochInstant: Instant, fullPath: Path, req: Reques
6868
response
6969
}
7070
case _ =>
71-
OptionT.liftF(logger.debug("No headers in query, service it")) >>
71+
OptionT.liftF(logger.debug("No If-Modified-Since headers in request")) >>
7272
service(req).map {
7373
resp =>
7474
respondWithCacheLastModified(

project/src/proxy.http.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ object HttpProxy:
8181
proxyPass: String,
8282
upstreams: Map[String, NonEmptyList[ProxyConfig.UpstreamServer]]
8383
): F[Uri] =
84-
println(s"proxypass $proxyPass")
85-
println(upstreams)
8684
if !proxyPass.contains("$") then Uri.fromString(proxyPass).liftTo[F]
8785
else
8886
extractVariable(proxyPass).flatMap {
@@ -140,8 +138,6 @@ object HttpProxy:
140138

141139
def xForwardedMiddleware[G[_], F[_]](http: Http[G, F]): Http[G, F] = Kleisli {
142140
(req: Request[F]) =>
143-
println("in middleware")
144-
println(req)
145141
req
146142
.remote
147143
.fold(http.run(req)) {

project/src/routes.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,10 @@ def routes[F[_]: Files: MonadThrow](
201201
dir / "index.html"
202202
)(logger)
203203

204-
Router(s"/$spaRoute" -> r)
204+
Router(spaRoute -> r)
205205

206206
val refreshRoutes = HttpRoutes.of[IO] {
207-
case GET -> Root / "api" / "v1" / "sse" =>
207+
case GET -> Root / "refresh" / "v1" / "sse" =>
208208
val keepAlive = fs2.Stream.fixedRate[IO](10.seconds).as(KeepAlive())
209209
Ok(
210210
keepAlive

project/test/src/RoutesSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class RoutesSuite extends CatsEffectSuite:
5757
// create a file in the folder
5858
val tempFile = tempDir / "index.html"
5959
os.write(tempFile, """<head><title>Test</title></head><body><h1>Test</h1></body>""")
60-
os.write(tempDir / "index.less", testStr)
60+
os.write(tempDir / "index.less", simpleCss)
6161
tempDir
6262
,
6363
teardown = tempDir =>
@@ -82,7 +82,7 @@ class RoutesSuite extends CatsEffectSuite:
8282
.root
8383
.clearHandlers()
8484
.clearModifiers()
85-
.withHandler(minimumLevel = Some(Level.get("trace").get))
85+
.withHandler(minimumLevel = Some(Level.get("info").get))
8686
.replace()
8787

8888
files.test("seed map puts files in the map on start") {
@@ -109,7 +109,7 @@ class RoutesSuite extends CatsEffectSuite:
109109
linkingTopic <- Topic[IO, Unit].toResource
110110
refreshTopic <- Topic[IO, Unit].toResource
111111
_ <- fileWatcher(fs2.io.file.Path(tempDir.toString), fileToHashRef, linkingTopic, refreshTopic)(logger)
112-
_ <- IO.sleep(100.millis).toResource // wait for watcher to start
112+
_ <- IO.sleep(200.millis).toResource // wait for watcher to start
113113
_ <- updateMapRef(tempDir.toFs2, fileToHashRef)(logger).toResource
114114
_ <- IO.blocking(os.write.over(tempDir / "test.js", "const hi = 'bye, world'")).toResource
115115
_ <- linkingTopic.publish1(()).toResource

0 commit comments

Comments
 (0)