<commit_before>// Copyright 2013 Alexandre Fiori
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Server-Sent events (SSE)
// http://dev.w3.org/html5/eventsource/
//
// Usage example:
//
//	func SSEHandler(w http.ResponseWriter, req *http.Request) {
//	        conn, err := sse.ServeEvents(w)
//	        if err != nil {
//	                http.Error(w, err.Error(), http.StatusInternalServerError)
//	                return
//	        }
//	        defer conn.Close()
//	        for i := 0; i < 10; i++ {
//	                sse.SendEvent(conn, &sse.MessageEvent{Data: "Hello, world"})
//	                time.Sleep(1 * time.Second)
//	        }
//	}
package sse

import (
	"bufio"
	"errors"
	"fmt"
	"net"
	"net/http"
)

var ErrNoHijack = errors.New("Server does not support hijacking")

// MessageEvent is the container of Server-Sent events (SSE), push notifications.
type MessageEvent struct {
	Data  string // message content
	Id    string // id of the message (int?)
	Event string // name of the event
	Retry int    // client reconnection time, in milliseconds
}

// ServeEvents prepares the request for SSE, push notifications.
// Caveat: ResponseWriter.Status() returns 0 instead of 200 after ServeEvents
// is called, and might break logging.
func ServeEvents(w http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Content-Type", "text/event-stream")
	hj, ok := w.(http.Hijacker)
	if !ok {
		return nil, nil, ErrNoHijack
	}
	conn, buf, err := hj.Hijack()
	fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\n")
	w.Header().Write(conn)
	fmt.Fprintf(conn, "\r\n")
	return conn, buf, err
}

// SendEvent sends a push notification to the peer (usually a browser).
// Browsers can handle these events in JavaScript:
// http://www.w3schools.com/html/html5_serversentevents.asp
func SendEvent(buf *bufio.ReadWriter, m *MessageEvent) (err error) {
	if m.Data != "" {
		fmt.Fprintf(buf, "data: %s\n", m.Data)
	}
	if m.Event != "" {
		fmt.Fprintf(buf, "event: %s\n", m.Event)
	}
	if m.Id != "" {
		fmt.Fprintf(buf, "id: %s\n", m.Id)
	}
	if m.Retry >= 1 {
		fmt.Fprintf(buf, "retry: %d\n", m.Retry)
	}
	_, err = fmt.Fprintf(buf, "\n")
	if err == nil {
		err = buf.Flush()
	}
	return
}
<commit_msg>Bug fixes in the SSE handler<commit_after>// Copyright 2013 Alexandre Fiori
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Server-Sent events (SSE)
// http://dev.w3.org/html5/eventsource/
//
// Usage example:
//
//	func SSEHandler(w http.ResponseWriter, req *http.Request) {
//	        conn, err := sse.ServeEvents(w)
//	        if err != nil {
//	                http.Error(w, err.Error(), http.StatusInternalServerError)
//	                return
//	        }
//	        defer conn.Close()
//	        for i := 0; i < 10; i++ {
//	                sse.SendEvent(conn, &sse.MessageEvent{Data: "Hello, world"})
//	                time.Sleep(1 * time.Second)
//	        }
//	}
package sse

import (
	"bufio"
	"errors"
	"fmt"
	"net"
	"net/http"
)

var ErrNoHijack = errors.New("Server does not support hijacking")

// MessageEvent is the container of Server-Sent events (SSE), push notifications.
type MessageEvent struct {
	Data  string // message content
	Id    string // id of the message (int?)
	Event string // name of the event
	Retry int    // client reconnection time, in milliseconds
}

// ServeEvents prepares the request for SSE, push notifications.
// Caveat: ResponseWriter.Status() returns 0 instead of 200 after ServeEvents
// is called, and might break logging.
func ServeEvents(w http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Content-Type", "text/event-stream")
	hj, ok := w.(http.Hijacker)
	if !ok {
		return nil, nil, ErrNoHijack
	}
	conn, rw, err := hj.Hijack()
	if err != nil {
		return nil, nil, err
	}
	fmt.Fprintf(rw, "HTTP/1.1 200 OK\r\n")
	w.Header().Write(rw)
	fmt.Fprintf(rw, "\r\n")
	return conn, rw, rw.Flush()
}

// SendEvent sends a push notification to the peer (usually a browser).
// Browsers can handle these events in JavaScript:
// http://www.w3schools.com/html/html5_serversentevents.asp
func SendEvent(rw *bufio.ReadWriter, m *MessageEvent) error {
	if m.Data != "" {
		fmt.Fprintf(rw, "data: %s\n", m.Data)
	}
	if m.Event != "" {
		fmt.Fprintf(rw, "event: %s\n", m.Event)
	}
	if m.Id != "" {
		fmt.Fprintf(rw, "id: %s\n", m.Id)
	}
	if m.Retry >= 1 {
		fmt.Fprintf(rw, "retry: %d\n", m.Retry)
	}
	fmt.Fprintf(rw, "\n")
	return rw.Flush()
}
<|endoftext|>


<commit_before>package shorty

import (
	"bytes"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	omni_http "github.com/qorio/omni/http"
	"net/http"
	"net/url"
	"strings"
	"text/template"
	"time"
)

var (
	deeplinkJsTemplate   = template.New("deeplink.js")
	openTestHtmlTemplate = template.New("opentest.html")
)

type appInstallInterstitialContext struct {
	Rule                  *RoutingRule
	IsCrossBrowserContext bool
	Timestamp             int64
}

func init() {
	var err error

	deeplinkJsTemplate, err = deeplinkJsTemplate.Parse(`
function getCookie(name) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length == 2) return parts.pop().split(";").shift();
}
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? null : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function redirectWithLocation(target) {
    if (/Safari/.test(navigator.userAgent)) {
        navigator.geolocation.getCurrentPosition(function(position) {
           lat = position.coords.latitude
           lng = position.coords.longitude
           window.location = target + "&lat=" + lat + "&lng=" + lng
        })
    } else {
        window.location = target
    }
}
function onLoad() {
    var deeplink = "{{.Rule.Destination}}";
    var appstore = "{{.Rule.AppStoreUrl}}";
    var interstitialUrl = window.location;
    var didNotDetectApp = getParameterByName('__xrl_noapp') != null;
    if (didNotDetectApp) {
{{if .IsCrossBrowserContext }}
        window.location = appstore;
{{else}}
        var el = document.getElementById("has-app")
        el.innerHTML = "
Still here?  Try open this in Safari.
";
{{end}}

    } else {
        var scheme = deeplink.split("://").shift();
        var shortCode = window.location.pathname.substring(1);
        deeplink += "&__xrlc=" + getCookie("uuid") + "&__xrlp=" + scheme + "&__xrls=" + shortCode;
        setTimeout(function() {
{{if eq .Rule.InterstitialToAppStoreOnTimeout "on"}}
              if (!document.webkitHidden) {
                  setTimeout(function(){
                      redirectWithLocation(interstitialUrl + "&__xrl_noapp=");
                  }, 2000)
                  window.location = appstore;
              }
{{else}}
              if (!document.webkitHidden) {
                  redirectWithLocation(interstitialUrl + "&__xrl_noapp=");
              }
{{end}}
        }, {{.Rule.InterstitialAppLinkTimeoutMillis}});
        window.location = deeplink;
    }
}
`)
	if err != nil {
		glog.Warningln("Bad template for deeplink.js!")
		panic(err)
	}

	openTestHtmlTemplate, err = openTestHtmlTemplate.Parse(`

  <title>Getting content...</title>

  <xmp theme="journal" style="display:none;">

{{if .IsCrossBrowserContext }}
  Install the app here.
{{else}}
  Opening the link in app...  If the app does not open, open this link via Safari.
{{end}}

  </xmp>

`)
	if err != nil {
		glog.Warningln("Bad template for html test/open!")
		panic(err)
	}
}

func (this *ShortyEndPoint) CheckAppInstallInterstitialHandler(resp http.ResponseWriter, req *http.Request) {
	req.ParseForm()
	_, noapp := req.Form[noAppInstallParam]

	vars := mux.Vars(req)
	uuid := vars["uuid"]
	shortUrl, err := this.service.FindUrl(vars["shortCode"])

	if err != nil {
		renderError(resp, req, err.Error(), http.StatusInternalServerError)
		return
	} else if shortUrl == nil {
		if this.settings.Redirect404 != "" {
			originalUrl, err := this.router.Get("redirect").URL("id", vars["id"])
			if err != nil {
				renderError(resp, req, err.Error(), http.StatusInternalServerError)
				return
			}
			url404 := strings.Replace(this.settings.Redirect404,
				"$origURL", url.QueryEscape(fmt.Sprintf("http://%s%s", req.Host, originalUrl.String())), 1)
			http.Redirect(resp, req, url404, http.StatusTemporaryRedirect)
			return
		}
		renderError(resp, req, "No URL was found with that shorty code", http.StatusNotFound)
		return
	}

	var content string = ""

	omni_http.SetNoCachingHeaders(resp)
	cookies := omni_http.NewCookieHandler(secureCookie, resp, req)

	userAgent := omni_http.ParseUserAgent(req)
	origin, _ := this.requestParser.Parse(req)

	matchedRule, _ := shortUrl.MatchRule(this.service, userAgent, origin, cookies)

	// visits, cookied, last, userId := processCookies(cookies, shortUrl)
	_, _, lastViewed, userId := processCookies(cookies, shortUrl.Id)
	glog.Infoln(">>> harvest - processed cookies", lastViewed, userId, shortUrl.Id, "matchedRule=", matchedRule)

	// Here we check if the two uuids are different.  One uuid is in the url of this request.  This is the uuid
	// from some context (e.g. from FB webview on iOS).  Another uuid is one in the cookie -- either we assigned
	// or read from the client context.  The current context may not be the same as the context of the uuid in
	// the url.  This is because the user could be visiting the same link from another browser (eg. on Safari)
	// after being prompted.
	// If the two uuids do not match -- then we know the contexts are different.  The user is visiting from
	// some context other than the one with the original link.  So in this case, we can do a redirect back to
	// the short link that the user was looking at that got them to see the harvest url in the first place.
	// Otherwise, show the static content which may tell them to try again in a different browser/context.

	appUrlScheme := ""

	if uuid != userId {

		glog.Infoln(">>>> harvest phase, noapp=", noapp)

		// We got the user to come here via a different context (browser) than the one that created
		// this url in the first place.  So link the two ids together and redirect back to the short url.

		if appUrlSchemeParam, exists := req.Form["s"]; exists {

			appUrlScheme = appUrlSchemeParam[0]
			this.service.Link(UrlScheme(appUrlScheme), UUID(uuid), UUID(userId), shortUrl.Id)

			// Now, look for an app-open in context of userId.  If we have somehow opened the app
			// before, then we can just create an app-open entry for *this* context (uuid) because
			// we know that the app already exists on the device and was opened in a different context.

			appOpen, found, _ := this.service.FindAppOpen(UrlScheme(appUrlScheme), UUID(userId))

			glog.Infoln("find app-open", appUrlScheme, userId, appOpen, found)

			if found {
				// create a record *as if* the app was also opened in the other context
				appOpen.SourceContext = UUID(uuid)
				appOpen.SourceApplication = origin.Referrer
				this.service.TrackAppOpen(UrlScheme(appUrlScheme), appOpen.AppContext, appOpen)
			}
		}
	}

	if fetchFromUrl, exists := req.Form["f"]; exists && fetchFromUrl[0] != "" {
		content = omni_http.FetchFromUrl(userAgent.Header, fetchFromUrl[0])
		resp.Write([]byte(content))
		return
	}

	if matchedRule != nil {
		openTestHtmlTemplate.Execute(resp, appInstallInterstitialContext{
			Rule: matchedRule,
			IsCrossBrowserContext: userId != uuid,
			Timestamp:             time.Now().Unix(),
		})
		return
	}

	return
}

func (this *ShortyEndPoint) CheckAppInstallInterstitialJSHandler(resp http.ResponseWriter, req *http.Request) {
	omni_http.SetNoCachingHeaders(resp)

	vars := mux.Vars(req)
	shortCode := vars["shortCode"]
	uuid := vars["uuid"]

	shortUrl, err := this.service.FindUrl(shortCode)
	if err != nil || shortUrl == nil {
		renderError(resp, req, err.Error(), http.StatusInternalServerError)
		return
	}

	cookies := omni_http.NewCookieHandler(secureCookie, resp, req)
	userAgent := omni_http.ParseUserAgent(req)
	origin, _ := this.requestParser.Parse(req)

	_, _, _, userId := processCookies(cookies, shortUrl.Id)

	// Hack -- set the referrer to be DIRECT, otherwise it's the interstitial page url
	origin.Referrer = "DIRECT"

	matchedRule, notFound := shortUrl.MatchRule(this.service, userAgent, origin, cookies)
	if notFound != nil {
		renderError(resp, req, "not found", http.StatusNotFound)
		return
	}

	glog.Infoln("Using matchedRule to generate from template", matchedRule)

	if userId != uuid {
		// This is the case where we harvest the uuid and we can safely redirect to
		// the appstore.
		matchedRule.InterstitialToAppStoreOnTimeout = "on"
	}

	context := &appInstallInterstitialContext{
		Rule: matchedRule,
		IsCrossBrowserContext: userId != uuid,
		Timestamp:             time.Now().Unix(),
	}

	var buff bytes.Buffer
	deeplinkJsTemplate.Execute(&buff, context)
	resp.Write(buff.Bytes())
	return
}
<commit_msg>Adding code in js to return geolocation<commit_after>package shorty

import (
	"bytes"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	omni_http "github.com/qorio/omni/http"
	"net/http"
	"net/url"
	"strings"
	"text/template"
	"time"
)

var (
	deeplinkJsTemplate   = template.New("deeplink.js")
	openTestHtmlTemplate = template.New("opentest.html")
)

type appInstallInterstitialContext struct {
	Rule                  *RoutingRule
	IsCrossBrowserContext bool
	Timestamp             int64
}

func init() {
	var err error

	deeplinkJsTemplate, err = deeplinkJsTemplate.Parse(`
function getCookie(name) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length == 2) return parts.pop().split(";").shift();
}
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? null : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function redirectWithLocation(target) {
    navigator.geolocation.getCurrentPosition(function(position) {
        lat = position.coords.latitude
        lng = position.coords.longitude
        window.location = target + "&lat=" + lat + "&lng=" + lng
    })
    /*
    if (/Safari/.test(navigator.userAgent)) {
    } else {
        window.location = target
    }*/
}
function onLoad() {
    var deeplink = "{{.Rule.Destination}}";
    var appstore = "{{.Rule.AppStoreUrl}}";
    var interstitialUrl = window.location;
    var didNotDetectApp = getParameterByName('__xrl_noapp') != null;
    if (didNotDetectApp) {
{{if .IsCrossBrowserContext }}
        window.location = appstore;
{{else}}
        var el = document.getElementById("has-app")
        el.innerHTML = "
Still here?  Try open this in Safari.
";
{{end}}

    } else {
        var scheme = deeplink.split("://").shift();
        var shortCode = window.location.pathname.substring(1);
        deeplink += "&__xrlc=" + getCookie("uuid") + "&__xrlp=" + scheme + "&__xrls=" + shortCode;
        setTimeout(function() {
{{if eq .Rule.InterstitialToAppStoreOnTimeout "on"}}
              if (!document.webkitHidden) {
                  setTimeout(function(){
                      redirectWithLocation(interstitialUrl + "&__xrl_noapp=");
                  }, 2000)
                  window.location = appstore;
              }
{{else}}
              if (!document.webkitHidden) {
                  redirectWithLocation(interstitialUrl + "&__xrl_noapp=");
              }
{{end}}
        }, {{.Rule.InterstitialAppLinkTimeoutMillis}});
        window.location = deeplink;
    }
}
`)
	if err != nil {
		glog.Warningln("Bad template for deeplink.js!")
		panic(err)
	}

	openTestHtmlTemplate, err = openTestHtmlTemplate.Parse(`

  <title>Getting content...</title>

  <xmp theme="journal" style="display:none;">

{{if .IsCrossBrowserContext }}
  Install the app here.
{{else}}
  Opening the link in app...  If the app does not open, open this link via Safari.
{{end}}

  </xmp>

`)
	if err != nil {
		glog.Warningln("Bad template for html test/open!")
		panic(err)
	}
}

func (this *ShortyEndPoint) CheckAppInstallInterstitialHandler(resp http.ResponseWriter, req *http.Request) {
	req.ParseForm()
	_, noapp := req.Form[noAppInstallParam]

	vars := mux.Vars(req)
	uuid := vars["uuid"]
	shortUrl, err := this.service.FindUrl(vars["shortCode"])

	if err != nil {
		renderError(resp, req, err.Error(), http.StatusInternalServerError)
		return
	} else if shortUrl == nil {
		if this.settings.Redirect404 != "" {
			originalUrl, err := this.router.Get("redirect").URL("id", vars["id"])
			if err != nil {
				renderError(resp, req, err.Error(), http.StatusInternalServerError)
				return
			}
			url404 := strings.Replace(this.settings.Redirect404,
				"$origURL", url.QueryEscape(fmt.Sprintf("http://%s%s", req.Host, originalUrl.String())), 1)
			http.Redirect(resp, req, url404, http.StatusTemporaryRedirect)
			return
		}
		renderError(resp, req, "No URL was found with that shorty code", http.StatusNotFound)
		return
	}

	var content string = ""

	omni_http.SetNoCachingHeaders(resp)
	cookies := omni_http.NewCookieHandler(secureCookie, resp, req)

	userAgent := omni_http.ParseUserAgent(req)
	origin, _ := this.requestParser.Parse(req)

	matchedRule, _ := shortUrl.MatchRule(this.service, userAgent, origin, cookies)

	// visits, cookied, last, userId := processCookies(cookies, shortUrl)
	_, _, lastViewed, userId := processCookies(cookies, shortUrl.Id)
	glog.Infoln(">>> harvest - processed cookies", lastViewed, userId, shortUrl.Id, "matchedRule=", matchedRule)

	// Here we check if the two uuids are different.  One uuid is in the url of this request.  This is the uuid
	// from some context (e.g. from FB webview on iOS).  Another uuid is one in the cookie -- either we assigned
	// or read from the client context.  The current context may not be the same as the context of the uuid in
	// the url.  This is because the user could be visiting the same link from another browser (eg. on Safari)
	// after being prompted.
	// If the two uuids do not match -- then we know the contexts are different.  The user is visiting from
	// some context other than the one with the original link.  So in this case, we can do a redirect back to
	// the short link that the user was looking at that got them to see the harvest url in the first place.
	// Otherwise, show the static content which may tell them to try again in a different browser/context.

	appUrlScheme := ""

	if uuid != userId {

		glog.Infoln(">>>> harvest phase, noapp=", noapp)

		// We got the user to come here via a different context (browser) than the one that created
		// this url in the first place.  So link the two ids together and redirect back to the short url.

		if appUrlSchemeParam, exists := req.Form["s"]; exists {

			appUrlScheme = appUrlSchemeParam[0]
			this.service.Link(UrlScheme(appUrlScheme), UUID(uuid), UUID(userId), shortUrl.Id)

			// Now, look for an app-open in context of userId.  If we have somehow opened the app
			// before, then we can just create an app-open entry for *this* context (uuid) because
			// we know that the app already exists on the device and was opened in a different context.

			appOpen, found, _ := this.service.FindAppOpen(UrlScheme(appUrlScheme), UUID(userId))

			glog.Infoln("find app-open", appUrlScheme, userId, appOpen, found)

			if found {
				// create a record *as if* the app was also opened in the other context
				appOpen.SourceContext = UUID(uuid)
				appOpen.SourceApplication = origin.Referrer
				this.service.TrackAppOpen(UrlScheme(appUrlScheme), appOpen.AppContext, appOpen)
			}
		}
	}

	if fetchFromUrl, exists := req.Form["f"]; exists && fetchFromUrl[0] != "" {
		content = omni_http.FetchFromUrl(userAgent.Header, fetchFromUrl[0])
		resp.Write([]byte(content))
		return
	}

	if matchedRule != nil {
		openTestHtmlTemplate.Execute(resp, appInstallInterstitialContext{
			Rule: matchedRule,
			IsCrossBrowserContext: userId != uuid,
			Timestamp:             time.Now().Unix(),
		})
		return
	}

	return
}

func (this *ShortyEndPoint) CheckAppInstallInterstitialJSHandler(resp http.ResponseWriter, req *http.Request) {
	omni_http.SetNoCachingHeaders(resp)

	vars := mux.Vars(req)
	shortCode := vars["shortCode"]
	uuid := vars["uuid"]

	shortUrl, err := this.service.FindUrl(shortCode)
	if err != nil || shortUrl == nil {
		renderError(resp, req, err.Error(), http.StatusInternalServerError)
		return
	}

	cookies := omni_http.NewCookieHandler(secureCookie, resp, req)
	userAgent := omni_http.ParseUserAgent(req)
	origin, _ := this.requestParser.Parse(req)

	_, _, _, userId := processCookies(cookies, shortUrl.Id)

	// Hack -- set the referrer to be DIRECT, otherwise it's the interstitial page url
	origin.Referrer = "DIRECT"

	matchedRule, notFound := shortUrl.MatchRule(this.service, userAgent, origin, cookies)
	if notFound != nil {
		renderError(resp, req, "not found", http.StatusNotFound)
		return
	}

	glog.Infoln("Using matchedRule to generate from template", matchedRule)

	if userId != uuid {
		// This is the case where we harvest the uuid and we can safely redirect to
		// the appstore.
		matchedRule.InterstitialToAppStoreOnTimeout = "on"
	}

	context := &appInstallInterstitialContext{
		Rule: matchedRule,
		IsCrossBrowserContext: userId != uuid,
		Timestamp:             time.Now().Unix(),
	}

	var buff bytes.Buffer
	deeplinkJsTemplate.Execute(&buff, context)
	resp.Write(buff.Bytes())
	return
}
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package e2e

import (
	"fmt"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"gotest.tools/v3/assert"
	"gotest.tools/v3/icmd"

	. "github.com/docker/compose-cli/utils/e2e"
)

var binDir string

func TestMain(m *testing.M) {
	p, cleanup, err := SetupExistingCLI()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	binDir = p
	exitCode := m.Run()
	cleanup()
	os.Exit(exitCode)
}

func TestLocalComposeUp(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	const projectName = "compose-e2e-demo"

	t.Run("up", func(t *testing.T) {
		c.RunDockerCmd("compose", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
	})

	t.Run("check accessing running app", func(t *testing.T) {
		res := c.RunDockerCmd("compose", "-p", projectName, "ps")
		res.Assert(t, icmd.Expected{Out: `web`})

		endpoint := "http://localhost:90"
		output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
		assert.Assert(t, strings.Contains(output, `"word":`))

		res = c.RunDockerCmd("network", "ls")
		res.Assert(t, icmd.Expected{Out: projectName + "_default"})
	})

	t.Run("top", func(t *testing.T) {
		res := c.RunDockerCmd("compose", "-p", projectName, "top")
		output := res.Stdout()
		assert.Assert(t, strings.Contains(output, `UID    PID     PPID    C    STIME   TTY   TIME       CMD`))
		assert.Assert(t, strings.Contains(output, `java -Xmx8m -Xms8m -jar /app/words.jar`))
		assert.Assert(t, strings.Contains(output, `/dispatcher`))
	})

	t.Run("check compose labels", func(t *testing.T) {
		wd, _ := os.Getwd()
		res := c.RunDockerCmd("inspect", projectName+"_web_1")
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "compose-e2e-demo"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "False",`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.config-hash":`})
		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"com.docker.compose.project.config_files": "%s/fixtures/sentences/compose.yaml"`, wd)})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project.working_dir":`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.service": "web"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version":`})

		res = c.RunDockerCmd("network", "inspect", projectName+"_default")
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.network": "default"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": `})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version": `})
	})

	t.Run("check user labels", func(t *testing.T) {
		res := c.RunDockerCmd("inspect", projectName+"_web_1")
		res.Assert(t, icmd.Expected{Out: `"my-label": "test"`})

	})

	t.Run("check healthcheck output", func(t *testing.T) {
		c.WaitForCmdResult(c.NewDockerCmd("compose", "-p", projectName, "ps", "--format", "json"),
			StdoutContains(`"Name":"compose-e2e-demo_web_1","Project":"compose-e2e-demo","Service":"web","State":"running","Health":"healthy"`),
			5*time.Second, 1*time.Second)

		res := c.RunDockerCmd("compose", "-p", projectName, "ps")
		res.Assert(t, icmd.Expected{Out: `NAME                       SERVICE             STATUS              PORTS`})
		res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_web_1     web                 running (healthy)   0.0.0.0:90->80/tcp`})
		res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_db_1      db                  running             5432/tcp`})
	})

	t.Run("down", func(t *testing.T) {
		_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
	})

	t.Run("check containers after down", func(t *testing.T) {
		res := c.RunDockerCmd("ps", "--all")
		assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
	})

	t.Run("check networks after down", func(t *testing.T) {
		res := c.RunDockerCmd("network", "ls")
		assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
	})
}

func TestComposePull(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	res := c.RunDockerOrExitError("compose", "--project-directory", "fixtures/simple-composefile", "pull")
	output := res.Combined()

	assert.Assert(t, strings.Contains(output, "simple Pulled"))
	assert.Assert(t, strings.Contains(output, "another Pulled"))
}

func TestAttachRestart(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	res := c.RunDockerOrExitError("compose", "--ansi=never", "--project-directory", "fixtures/attach-restart", "up")
	output := res.Stdout()

	assert.Assert(t, strings.Contains(output, `another_1  | world
attach-restart_another_1 exited with code 1
another_1  | world
attach-restart_another_1 exited with code 1
another_1  | world
attach-restart_another_1 exited with code 1`), res.Combined())
}
<commit_msg>Fix log expectation with random exec order<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package e2e

import (
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strings"
	"testing"
	"time"

	"gotest.tools/v3/assert"
	"gotest.tools/v3/icmd"

	. "github.com/docker/compose-cli/utils/e2e"
)

var binDir string

func TestMain(m *testing.M) {
	p, cleanup, err := SetupExistingCLI()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	binDir = p
	exitCode := m.Run()
	cleanup()
	os.Exit(exitCode)
}

func TestLocalComposeUp(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	const projectName = "compose-e2e-demo"

	t.Run("up", func(t *testing.T) {
		c.RunDockerCmd("compose", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
	})

	t.Run("check accessing running app", func(t *testing.T) {
		res := c.RunDockerCmd("compose", "-p", projectName, "ps")
		res.Assert(t, icmd.Expected{Out: `web`})

		endpoint := "http://localhost:90"
		output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
		assert.Assert(t, strings.Contains(output, `"word":`))

		res = c.RunDockerCmd("network", "ls")
		res.Assert(t, icmd.Expected{Out: projectName + "_default"})
	})

	t.Run("top", func(t *testing.T) {
		res := c.RunDockerCmd("compose", "-p", projectName, "top")
		output := res.Stdout()
		assert.Assert(t, strings.Contains(output, `UID    PID     PPID    C    STIME   TTY   TIME       CMD`))
		assert.Assert(t, strings.Contains(output, `java -Xmx8m -Xms8m -jar /app/words.jar`))
		assert.Assert(t, strings.Contains(output, `/dispatcher`))
	})

	t.Run("check compose labels", func(t *testing.T) {
		wd, _ := os.Getwd()
		res := c.RunDockerCmd("inspect", projectName+"_web_1")
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "compose-e2e-demo"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "False",`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.config-hash":`})
		res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"com.docker.compose.project.config_files": "%s/fixtures/sentences/compose.yaml"`, wd)})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project.working_dir":`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.service": "web"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version":`})

		res = c.RunDockerCmd("network", "inspect", projectName+"_default")
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.network": "default"`})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": `})
		res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version": `})
	})

	t.Run("check user labels", func(t *testing.T) {
		res := c.RunDockerCmd("inspect", projectName+"_web_1")
		res.Assert(t, icmd.Expected{Out: `"my-label": "test"`})

	})

	t.Run("check healthcheck output", func(t *testing.T) {
		c.WaitForCmdResult(c.NewDockerCmd("compose", "-p", projectName, "ps", "--format", "json"),
			StdoutContains(`"Name":"compose-e2e-demo_web_1","Project":"compose-e2e-demo","Service":"web","State":"running","Health":"healthy"`),
			5*time.Second, 1*time.Second)

		res := c.RunDockerCmd("compose", "-p", projectName, "ps")
		res.Assert(t, icmd.Expected{Out: `NAME                       SERVICE             STATUS              PORTS`})
		res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_web_1     web                 running (healthy)   0.0.0.0:90->80/tcp`})
		res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_db_1      db                  running             5432/tcp`})
	})

	t.Run("down", func(t *testing.T) {
		_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
	})

	t.Run("check containers after down", func(t *testing.T) {
		res := c.RunDockerCmd("ps", "--all")
		assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
	})

	t.Run("check networks after down", func(t *testing.T) {
		res := c.RunDockerCmd("network", "ls")
		assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
	})
}

func TestComposePull(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	res := c.RunDockerOrExitError("compose", "--project-directory", "fixtures/simple-composefile", "pull")
	output := res.Combined()

	assert.Assert(t, strings.Contains(output, "simple Pulled"))
	assert.Assert(t, strings.Contains(output, "another Pulled"))
}

func TestAttachRestart(t *testing.T) {
	c := NewParallelE2eCLI(t, binDir)

	res := c.RunDockerOrExitError("compose", "--ansi=never", "--project-directory", "fixtures/attach-restart", "up")
	output := res.Stdout()

	exitRegex := regexp.MustCompile("attach-restart_another_1 exited with code 1")
	assert.Equal(t, len(exitRegex.FindAllStringIndex(output, -1)), 3, res.Combined())

	execRegex := regexp.MustCompile(`another_1  \| world`)
	assert.Equal(t, len(execRegex.FindAllStringIndex(output, -1)), 3, res.Combined())
}
<|endoftext|>