github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/gui/upgradegui_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gui_test
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"runtime"
    18  	"strings"
    19  
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/version"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/juju/juju/api/controller"
    25  	"github.com/juju/juju/apiserver/params"
    26  	"github.com/juju/juju/cmd/juju/gui"
    27  	envgui "github.com/juju/juju/environs/gui"
    28  	"github.com/juju/juju/environs/simplestreams"
    29  	jujutesting "github.com/juju/juju/juju/testing"
    30  	coretesting "github.com/juju/juju/testing"
    31  )
    32  
    33  type upgradeGUISuite struct {
    34  	jujutesting.JujuConnSuite
    35  }
    36  
    37  var _ = gc.Suite(&upgradeGUISuite{})
    38  
    39  // run executes the upgrade-gui command passing the given args.
    40  func (s *upgradeGUISuite) run(c *gc.C, args ...string) (string, error) {
    41  	ctx, err := coretesting.RunCommand(c, gui.NewUpgradeGUICommand(), args...)
    42  	return strings.Trim(coretesting.Stderr(ctx), "\n"), err
    43  }
    44  
    45  // calledFunc is returned by the patch* methods below, and when called reports
    46  // whether the corresponding patched function has been called.
    47  type calledFunc func() bool
    48  
    49  func (s *upgradeGUISuite) patchClientGUIArchives(c *gc.C, returnedVersions []params.GUIArchiveVersion, returnedErr error) calledFunc {
    50  	var called bool
    51  	f := func(client *controller.Client) ([]params.GUIArchiveVersion, error) {
    52  		called = true
    53  		return returnedVersions, returnedErr
    54  	}
    55  	s.PatchValue(gui.ClientGUIArchives, f)
    56  	return func() bool {
    57  		return called
    58  	}
    59  }
    60  
    61  func (s *upgradeGUISuite) patchClientSelectGUIVersion(c *gc.C, expectedVers string, returnedErr error) calledFunc {
    62  	var called bool
    63  	f := func(client *controller.Client, vers version.Number) error {
    64  		called = true
    65  		c.Assert(vers.String(), gc.Equals, expectedVers)
    66  		return returnedErr
    67  	}
    68  	s.PatchValue(gui.ClientSelectGUIVersion, f)
    69  	return func() bool {
    70  		return called
    71  	}
    72  }
    73  
    74  func (s *upgradeGUISuite) patchClientUploadGUIArchive(c *gc.C, expectedHash string, expectedSize int64, expectedVers string, returnedIsCurrent bool, returnedErr error) calledFunc {
    75  	var called bool
    76  	f := func(client *controller.Client, r io.ReadSeeker, hash string, size int64, vers version.Number) (bool, error) {
    77  		called = true
    78  		c.Assert(hash, gc.Equals, expectedHash)
    79  		c.Assert(size, gc.Equals, expectedSize)
    80  		c.Assert(vers.String(), gc.Equals, expectedVers)
    81  		return returnedIsCurrent, returnedErr
    82  	}
    83  	s.PatchValue(gui.ClientUploadGUIArchive, f)
    84  	return func() bool {
    85  		return called
    86  	}
    87  }
    88  
    89  func (s *upgradeGUISuite) patchGUIFetchMetadata(c *gc.C, returnedMetadata []*envgui.Metadata, returnedErr error) calledFunc {
    90  	var called bool
    91  	f := func(stream string, sources ...simplestreams.DataSource) ([]*envgui.Metadata, error) {
    92  		called = true
    93  		c.Assert(stream, gc.Equals, envgui.ReleasedStream)
    94  		c.Assert(sources[0].Description(), gc.Equals, "gui simplestreams")
    95  		return returnedMetadata, returnedErr
    96  	}
    97  	s.PatchValue(gui.GUIFetchMetadata, f)
    98  	return func() bool {
    99  		return called
   100  	}
   101  }
   102  
   103  var upgradeGUIInputErrorsTests = []struct {
   104  	about         string
   105  	args          []string
   106  	expectedError string
   107  }{{
   108  	about:         "too many arguments",
   109  	args:          []string{"bad", "wolf"},
   110  	expectedError: `unrecognized args: \["bad" "wolf"\]`,
   111  }, {
   112  	about:         "listing and upgrading",
   113  	args:          []string{"bad", "--list"},
   114  	expectedError: "cannot provide arguments if --list is provided",
   115  }, {
   116  	about:         "archive path not found",
   117  	args:          []string{"no-such-file"},
   118  	expectedError: `invalid GUI release version or local path "no-such-file"`,
   119  }}
   120  
   121  func (s *upgradeGUISuite) TestUpgradeGUIInputErrors(c *gc.C) {
   122  	for i, test := range upgradeGUIInputErrorsTests {
   123  		c.Logf("\n%d: %s", i, test.about)
   124  		_, err := s.run(c, test.args...)
   125  		c.Assert(err, gc.ErrorMatches, test.expectedError)
   126  	}
   127  }
   128  
   129  func (s *upgradeGUISuite) TestUpgradeGUIListSuccess(c *gc.C) {
   130  	s.patchGUIFetchMetadata(c, []*envgui.Metadata{{
   131  		Version: version.MustParse("2.2.0"),
   132  	}, {
   133  		Version: version.MustParse("2.1.1"),
   134  	}, {
   135  		Version: version.MustParse("2.1.0"),
   136  	}}, nil)
   137  	uploadCalled := s.patchClientUploadGUIArchive(c, "", 0, "", false, nil)
   138  	selectCalled := s.patchClientSelectGUIVersion(c, "", nil)
   139  
   140  	// Run the command to list available Juju GUI archive versions.
   141  	out, err := s.run(c, "--list")
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	c.Assert(out, gc.Equals, "2.2.0\n2.1.1\n2.1.0")
   144  
   145  	// No uploads or switches are preformed.
   146  	c.Assert(uploadCalled(), jc.IsFalse)
   147  	c.Assert(selectCalled(), jc.IsFalse)
   148  }
   149  
   150  func (s *upgradeGUISuite) TestUpgradeGUIListNoReleases(c *gc.C) {
   151  	s.patchGUIFetchMetadata(c, nil, nil)
   152  	out, err := s.run(c, "--list")
   153  	c.Assert(err, gc.ErrorMatches, "cannot list Juju GUI release versions: no available Juju GUI archives found")
   154  	c.Assert(out, gc.Equals, "")
   155  }
   156  
   157  func (s *upgradeGUISuite) TestUpgradeGUIListError(c *gc.C) {
   158  	s.patchGUIFetchMetadata(c, nil, errors.New("bad wolf"))
   159  	out, err := s.run(c, "--list")
   160  	c.Assert(err, gc.ErrorMatches, "cannot list Juju GUI release versions: cannot retrieve Juju GUI archive info: bad wolf")
   161  	c.Assert(out, gc.Equals, "")
   162  }
   163  
   164  func (s *upgradeGUISuite) TestUpgradeGUIFileError(c *gc.C) {
   165  	path, _, _ := saveGUIArchive(c, "2.0.0")
   166  	err := os.Chmod(path, 0000)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	defer os.Chmod(path, 0600)
   169  	out, err := s.run(c, path)
   170  	c.Assert(err, gc.ErrorMatches, "cannot open GUI archive: .*")
   171  	c.Assert(out, gc.Equals, "")
   172  }
   173  
   174  func (s *upgradeGUISuite) TestUpgradeGUIArchiveVersionNotValid(c *gc.C) {
   175  	path, _, _ := saveGUIArchive(c, "bad-wolf")
   176  	out, err := s.run(c, path)
   177  	c.Assert(err, gc.ErrorMatches, `cannot upgrade Juju GUI using ".*": invalid version "bad-wolf" in archive`)
   178  	c.Assert(out, gc.Equals, "")
   179  }
   180  
   181  func (s *upgradeGUISuite) TestUpgradeGUIArchiveVersionNotFound(c *gc.C) {
   182  	path, _, _ := saveGUIArchive(c, "")
   183  	out, err := s.run(c, path)
   184  	c.Assert(err, gc.ErrorMatches, `cannot upgrade Juju GUI using ".*": cannot find Juju GUI version in archive`)
   185  	c.Assert(out, gc.Equals, "")
   186  }
   187  
   188  func (s *upgradeGUISuite) TestUpgradeGUIGUIArchivesError(c *gc.C) {
   189  	path, _, _ := saveGUIArchive(c, "2.1.0")
   190  	s.patchClientGUIArchives(c, nil, errors.New("bad wolf"))
   191  	out, err := s.run(c, path)
   192  	c.Assert(err, gc.ErrorMatches, "cannot retrieve GUI versions from the controller: bad wolf")
   193  	c.Assert(out, gc.Equals, "")
   194  }
   195  
   196  func (s *upgradeGUISuite) TestUpgradeGUIUploadGUIArchiveError(c *gc.C) {
   197  	path, hash, size := saveGUIArchive(c, "2.2.0")
   198  	s.patchClientGUIArchives(c, nil, nil)
   199  	s.patchClientUploadGUIArchive(c, hash, size, "2.2.0", false, errors.New("bad wolf"))
   200  	out, err := s.run(c, path)
   201  	c.Assert(err, gc.ErrorMatches, "cannot upload Juju GUI: bad wolf")
   202  	c.Assert(out, gc.Equals, "using local Juju GUI archive\nuploading Juju GUI 2.2.0")
   203  }
   204  
   205  func (s *upgradeGUISuite) TestUpgradeGUISelectGUIVersionError(c *gc.C) {
   206  	path, hash, size := saveGUIArchive(c, "2.3.0")
   207  	s.patchClientGUIArchives(c, nil, nil)
   208  	s.patchClientUploadGUIArchive(c, hash, size, "2.3.0", false, nil)
   209  	s.patchClientSelectGUIVersion(c, "2.3.0", errors.New("bad wolf"))
   210  	out, err := s.run(c, path)
   211  	c.Assert(err, gc.ErrorMatches, "cannot switch to new Juju GUI version: bad wolf")
   212  	c.Assert(out, gc.Equals, "using local Juju GUI archive\nuploading Juju GUI 2.3.0\nupload completed")
   213  }
   214  
   215  func (s *upgradeGUISuite) TestUpgradeGUIFromSimplestreamsReleaseErrors(c *gc.C) {
   216  	tests := []struct {
   217  		about            string
   218  		arg              string
   219  		returnedMetadata []*envgui.Metadata
   220  		returnedErr      error
   221  		expectedErr      string
   222  	}{{
   223  		about:       "last release: no releases found",
   224  		expectedErr: "cannot upgrade to most recent release: no available Juju GUI archives found",
   225  	}, {
   226  		about:       "specific release: no releases found",
   227  		arg:         "2.0.42",
   228  		expectedErr: "cannot upgrade to release 2.0.42: no available Juju GUI archives found",
   229  	}, {
   230  		about:       "last release: error while fetching releases list",
   231  		returnedErr: errors.New("bad wolf"),
   232  		expectedErr: "cannot upgrade to most recent release: cannot retrieve Juju GUI archive info: bad wolf",
   233  	}, {
   234  		about:       "specific release: error while fetching releases list",
   235  		arg:         "2.0.47",
   236  		returnedErr: errors.New("bad wolf"),
   237  		expectedErr: "cannot upgrade to release 2.0.47: cannot retrieve Juju GUI archive info: bad wolf",
   238  	}, {
   239  		about: "last release: error while opening the remote release resource",
   240  		returnedMetadata: []*envgui.Metadata{
   241  			makeGUIMetadata(c, "2.2.0", "exterminate"),
   242  			makeGUIMetadata(c, "2.1.0", ""),
   243  		},
   244  		expectedErr: `cannot open Juju GUI archive at "https://1.2.3.4/path/to/gui/2.2.0": exterminate`,
   245  	}, {
   246  		about: "specific release: error while opening the remote release resource",
   247  		arg:   "2.1.0",
   248  		returnedMetadata: []*envgui.Metadata{
   249  			makeGUIMetadata(c, "2.2.0", ""),
   250  			makeGUIMetadata(c, "2.1.0", "boo"),
   251  			makeGUIMetadata(c, "2.0.0", ""),
   252  		},
   253  		expectedErr: `cannot open Juju GUI archive at "https://1.2.3.4/path/to/gui/2.1.0": boo`,
   254  	}, {
   255  		about: "specific release: not found in available releases",
   256  		arg:   "2.1.0",
   257  		returnedMetadata: []*envgui.Metadata{
   258  			makeGUIMetadata(c, "2.2.0", ""),
   259  			makeGUIMetadata(c, "2.0.0", ""),
   260  		},
   261  		expectedErr: "Juju GUI release version 2.1.0 not found",
   262  	}}
   263  
   264  	for i, test := range tests {
   265  		c.Logf("\n%d: %s", i, test.about)
   266  
   267  		s.patchGUIFetchMetadata(c, test.returnedMetadata, test.returnedErr)
   268  		out, err := s.run(c, test.arg)
   269  		c.Assert(err, gc.ErrorMatches, test.expectedErr)
   270  		c.Assert(out, gc.Equals, "")
   271  	}
   272  }
   273  
   274  func (s *upgradeGUISuite) TestUpgradeGUISuccess(c *gc.C) {
   275  	tests := []struct {
   276  		// about describes the test.
   277  		about string
   278  		// returnedMetadata holds metadata information returned by simplestreams.
   279  		returnedMetadata *envgui.Metadata
   280  		// archiveVersion is the version of the archive to be uploaded.
   281  		archiveVersion string
   282  		// existingVersions is a function returning a list of GUI archive versions
   283  		// already included in the controller.
   284  		existingVersions func(hash string) []params.GUIArchiveVersion
   285  		// opened holds whether Juju GUI metadata information in simplestreams
   286  		// has been opened.
   287  		opened bool
   288  		// uploaded holds whether the archive has been actually uploaded. If an
   289  		// archive with the same hash and version is already present in the
   290  		// controller, the upload is not performed again.
   291  		uploaded bool
   292  		// selected holds whether a new GUI version must be selected. If the upload
   293  		// upgraded the currently served version there is no need to perform
   294  		// the API call to switch GUI version.
   295  		selected bool
   296  		// expectedOutput holds the expected upgrade-gui command output.
   297  		expectedOutput string
   298  	}{{
   299  		about:          "archive: first archive",
   300  		archiveVersion: "2.0.0",
   301  		expectedOutput: "using local Juju GUI archive\nuploading Juju GUI 2.0.0\nupload completed\nJuju GUI switched to version 2.0.0",
   302  		uploaded:       true,
   303  		selected:       true,
   304  	}, {
   305  		about:          "archive: new archive",
   306  		archiveVersion: "2.1.0",
   307  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   308  			return []params.GUIArchiveVersion{{
   309  				Version: version.MustParse("1.0.0"),
   310  				SHA256:  "hash-1",
   311  				Current: true,
   312  			}}
   313  		},
   314  		uploaded:       true,
   315  		selected:       true,
   316  		expectedOutput: "using local Juju GUI archive\nuploading Juju GUI 2.1.0\nupload completed\nJuju GUI switched to version 2.1.0",
   317  	}, {
   318  		about:          "archive: new archive, existing non-current version",
   319  		archiveVersion: "2.0.42",
   320  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   321  			return []params.GUIArchiveVersion{{
   322  				Version: version.MustParse("2.0.42"),
   323  				SHA256:  "hash-42",
   324  				Current: false,
   325  			}, {
   326  				Version: version.MustParse("2.0.47"),
   327  				SHA256:  "hash-47",
   328  				Current: true,
   329  			}}
   330  		},
   331  		uploaded:       true,
   332  		selected:       true,
   333  		expectedOutput: "using local Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42",
   334  	}, {
   335  		about:          "archive: new archive, existing current version",
   336  		archiveVersion: "2.0.47",
   337  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   338  			return []params.GUIArchiveVersion{{
   339  				Version: version.MustParse("2.0.47"),
   340  				SHA256:  "hash-47",
   341  				Current: true,
   342  			}}
   343  		},
   344  		uploaded:       true,
   345  		expectedOutput: "using local Juju GUI archive\nuploading Juju GUI 2.0.47\nupload completed\nJuju GUI at version 2.0.47",
   346  	}, {
   347  		about:          "archive: existing archive, existing non-current version",
   348  		archiveVersion: "2.0.42",
   349  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   350  			return []params.GUIArchiveVersion{{
   351  				Version: version.MustParse("2.0.42"),
   352  				SHA256:  hash,
   353  				Current: false,
   354  			}, {
   355  				Version: version.MustParse("2.0.47"),
   356  				SHA256:  "hash-47",
   357  				Current: true,
   358  			}}
   359  		},
   360  		selected:       true,
   361  		expectedOutput: "Juju GUI switched to version 2.0.42",
   362  	}, {
   363  		about:          "archive: existing archive, existing current version",
   364  		archiveVersion: "1.47.0",
   365  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   366  			return []params.GUIArchiveVersion{{
   367  				Version: version.MustParse("1.47.0"),
   368  				SHA256:  hash,
   369  				Current: true,
   370  			}}
   371  		},
   372  		expectedOutput: "Juju GUI at version 1.47.0",
   373  	}, {
   374  		about:          "archive: existing archive, different existing version",
   375  		archiveVersion: "2.0.42",
   376  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   377  			return []params.GUIArchiveVersion{{
   378  				Version: version.MustParse("2.0.42"),
   379  				SHA256:  "hash-42",
   380  				Current: false,
   381  			}, {
   382  				Version: version.MustParse("2.0.47"),
   383  				SHA256:  hash,
   384  				Current: true,
   385  			}}
   386  		},
   387  		uploaded:       true,
   388  		selected:       true,
   389  		expectedOutput: "using local Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42",
   390  	}, {
   391  		about:            "stream: first archive",
   392  		archiveVersion:   "2.0.0",
   393  		returnedMetadata: makeGUIMetadata(c, "2.0.0", ""),
   394  		expectedOutput:   "fetching Juju GUI archive\nuploading Juju GUI 2.0.0\nupload completed\nJuju GUI switched to version 2.0.0",
   395  		opened:           true,
   396  		uploaded:         true,
   397  		selected:         true,
   398  	}, {
   399  		about:            "stream: new archive",
   400  		archiveVersion:   "2.1.0",
   401  		returnedMetadata: makeGUIMetadata(c, "2.1.0", ""),
   402  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   403  			return []params.GUIArchiveVersion{{
   404  				Version: version.MustParse("1.0.0"),
   405  				SHA256:  "hash-1",
   406  				Current: true,
   407  			}}
   408  		},
   409  		opened:         true,
   410  		uploaded:       true,
   411  		selected:       true,
   412  		expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.1.0\nupload completed\nJuju GUI switched to version 2.1.0",
   413  	}, {
   414  		about:            "stream: new archive, existing non-current version",
   415  		archiveVersion:   "2.0.42",
   416  		returnedMetadata: makeGUIMetadata(c, "2.0.42", ""),
   417  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   418  			return []params.GUIArchiveVersion{{
   419  				Version: version.MustParse("2.0.42"),
   420  				SHA256:  "hash-42",
   421  				Current: false,
   422  			}, {
   423  				Version: version.MustParse("2.0.47"),
   424  				SHA256:  "hash-47",
   425  				Current: true,
   426  			}}
   427  		},
   428  		opened:         true,
   429  		uploaded:       true,
   430  		selected:       true,
   431  		expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42",
   432  	}, {
   433  		about:            "stream: new archive, existing current version",
   434  		archiveVersion:   "2.0.47",
   435  		returnedMetadata: makeGUIMetadata(c, "2.0.47", ""),
   436  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   437  			return []params.GUIArchiveVersion{{
   438  				Version: version.MustParse("2.0.47"),
   439  				SHA256:  "hash-47",
   440  				Current: true,
   441  			}}
   442  		},
   443  		opened:         true,
   444  		uploaded:       true,
   445  		expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.47\nupload completed\nJuju GUI at version 2.0.47",
   446  	}, {
   447  		about:            "stream: existing archive, existing non-current version",
   448  		archiveVersion:   "2.0.42",
   449  		returnedMetadata: makeGUIMetadata(c, "2.0.42", ""),
   450  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   451  			return []params.GUIArchiveVersion{{
   452  				Version: version.MustParse("2.0.42"),
   453  				SHA256:  hash,
   454  				Current: false,
   455  			}, {
   456  				Version: version.MustParse("2.0.47"),
   457  				SHA256:  "hash-47",
   458  				Current: true,
   459  			}}
   460  		},
   461  		opened:         true,
   462  		selected:       true,
   463  		expectedOutput: "Juju GUI switched to version 2.0.42",
   464  	}, {
   465  		about:            "stream: existing archive, existing current version",
   466  		archiveVersion:   "1.47.0",
   467  		returnedMetadata: makeGUIMetadata(c, "1.47.0", ""),
   468  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   469  			return []params.GUIArchiveVersion{{
   470  				Version: version.MustParse("1.47.0"),
   471  				SHA256:  hash,
   472  				Current: true,
   473  			}}
   474  		},
   475  		opened:         true,
   476  		expectedOutput: "Juju GUI at version 1.47.0",
   477  	}, {
   478  		about:            "stream: existing archive, different existing version",
   479  		archiveVersion:   "2.0.42",
   480  		returnedMetadata: makeGUIMetadata(c, "2.0.42", ""),
   481  		existingVersions: func(hash string) []params.GUIArchiveVersion {
   482  			return []params.GUIArchiveVersion{{
   483  				Version: version.MustParse("2.0.42"),
   484  				SHA256:  "hash-42",
   485  				Current: false,
   486  			}, {
   487  				Version: version.MustParse("2.0.47"),
   488  				SHA256:  hash,
   489  				Current: true,
   490  			}}
   491  		},
   492  		opened:         true,
   493  		uploaded:       true,
   494  		selected:       true,
   495  		expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42",
   496  	}}
   497  
   498  	for i, test := range tests {
   499  		c.Logf("\n%d: %s", i, test.about)
   500  
   501  		var arg string
   502  		var hash string
   503  		var size int64
   504  
   505  		if test.returnedMetadata == nil {
   506  			// Create an fake Juju GUI local archive.
   507  			arg, hash, size = saveGUIArchive(c, test.archiveVersion)
   508  		} else {
   509  			// Use the remote metadata information.
   510  			arg = test.returnedMetadata.Version.String()
   511  			hash = test.returnedMetadata.SHA256
   512  			size = test.returnedMetadata.Size
   513  		}
   514  
   515  		// Patch the call to get simplestreams metadata information.
   516  		fetchMetadataCalled := s.patchGUIFetchMetadata(c, []*envgui.Metadata{test.returnedMetadata}, nil)
   517  
   518  		// Patch the call to get existing archive versions.
   519  		var existingVersions []params.GUIArchiveVersion
   520  		if test.existingVersions != nil {
   521  			existingVersions = test.existingVersions(hash)
   522  		}
   523  		guiArchivesCalled := s.patchClientGUIArchives(c, existingVersions, nil)
   524  
   525  		// Patch the other calls to the controller.
   526  		uploadGUIArchiveCalled := s.patchClientUploadGUIArchive(c, hash, size, test.archiveVersion, !test.selected, nil)
   527  		selectGUIVersionCalled := s.patchClientSelectGUIVersion(c, test.archiveVersion, nil)
   528  
   529  		// Run the command.
   530  		out, err := s.run(c, arg)
   531  		c.Assert(err, jc.ErrorIsNil)
   532  		c.Assert(out, gc.Equals, test.expectedOutput)
   533  		c.Assert(guiArchivesCalled(), jc.IsTrue)
   534  		c.Assert(fetchMetadataCalled(), gc.Equals, test.opened)
   535  		c.Assert(uploadGUIArchiveCalled(), gc.Equals, test.uploaded)
   536  		c.Assert(selectGUIVersionCalled(), gc.Equals, test.selected)
   537  	}
   538  }
   539  
   540  func (s *upgradeGUISuite) TestUpgradeGUIIntegration(c *gc.C) {
   541  	// Prepare a GUI archive.
   542  	path, hash, size := saveGUIArchive(c, "2.42.0")
   543  
   544  	// Upload the archive from command line.
   545  	out, err := s.run(c, path)
   546  	c.Assert(err, jc.ErrorIsNil)
   547  	c.Assert(out, gc.Equals, "using local Juju GUI archive\nuploading Juju GUI 2.42.0\nupload completed\nJuju GUI switched to version 2.42.0")
   548  
   549  	// Check that the archive is present in the GUI storage server side.
   550  	storage, err := s.State.GUIStorage()
   551  	c.Assert(err, jc.ErrorIsNil)
   552  	defer storage.Close()
   553  	metadata, err := storage.Metadata("2.42.0")
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	c.Assert(metadata.SHA256, gc.Equals, hash)
   556  	c.Assert(metadata.Size, gc.Equals, size)
   557  
   558  	// Check that the uploaded version has been set as the current one.
   559  	vers, err := s.State.GUIVersion()
   560  	c.Assert(err, jc.ErrorIsNil)
   561  	c.Assert(vers.String(), gc.Equals, "2.42.0")
   562  }
   563  
   564  // makeGUIArchive creates a Juju GUI tar.bz2 archive in memory, and returns a
   565  // reader for the archive, its SHA256 hash and size.
   566  func makeGUIArchive(c *gc.C, vers string) (r io.Reader, hash string, size int64) {
   567  	if runtime.GOOS == "windows" {
   568  		c.Skip("bzip2 command not available")
   569  	}
   570  	cmd := exec.Command("bzip2", "--compress", "--stdout", "--fast")
   571  
   572  	stdin, err := cmd.StdinPipe()
   573  	c.Assert(err, jc.ErrorIsNil)
   574  	stdout, err := cmd.StdoutPipe()
   575  	c.Assert(err, jc.ErrorIsNil)
   576  
   577  	err = cmd.Start()
   578  	c.Assert(err, jc.ErrorIsNil)
   579  
   580  	tw := tar.NewWriter(stdin)
   581  	if vers != "" {
   582  		err = tw.WriteHeader(&tar.Header{
   583  			Name:     filepath.Join("jujugui-"+vers, "jujugui"),
   584  			Mode:     0700,
   585  			Typeflag: tar.TypeDir,
   586  		})
   587  		c.Assert(err, jc.ErrorIsNil)
   588  	}
   589  	err = tw.Close()
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	err = stdin.Close()
   592  	c.Assert(err, jc.ErrorIsNil)
   593  
   594  	h := sha256.New()
   595  	r = io.TeeReader(stdout, h)
   596  	b, err := ioutil.ReadAll(r)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  
   599  	err = cmd.Wait()
   600  	c.Assert(err, jc.ErrorIsNil)
   601  
   602  	return bytes.NewReader(b), fmt.Sprintf("%x", h.Sum(nil)), int64(len(b))
   603  }
   604  
   605  // saveGUIArchive creates a Juju GUI tar.bz2 archive with the given version on
   606  // disk, and return its path, SHA256 hash and size.
   607  func saveGUIArchive(c *gc.C, vers string) (path, hash string, size int64) {
   608  	r, hash, size := makeGUIArchive(c, vers)
   609  	path = filepath.Join(c.MkDir(), "gui.tar.bz2")
   610  	data, err := ioutil.ReadAll(r)
   611  	c.Assert(err, jc.ErrorIsNil)
   612  	err = ioutil.WriteFile(path, data, 0600)
   613  	c.Assert(err, jc.ErrorIsNil)
   614  	return path, hash, size
   615  }
   616  
   617  // makeGUIMetadata creates and return a Juju GUI archive metadata with the
   618  // given version. If fetchError is not empty, trying to fetch the corresponding
   619  // archive will return the given error.
   620  func makeGUIMetadata(c *gc.C, vers, fetchError string) *envgui.Metadata {
   621  	path, hash, size := saveGUIArchive(c, vers)
   622  	metaPath := "/path/to/gui/" + vers
   623  	return &envgui.Metadata{
   624  		Version:  version.MustParse(vers),
   625  		SHA256:   hash,
   626  		Size:     size,
   627  		Path:     metaPath,
   628  		FullPath: "https://1.2.3.4" + metaPath,
   629  		Source: &dataSource{
   630  			DataSource: envgui.NewDataSource("htpps://1.2.3.4"),
   631  			metaPath:   metaPath,
   632  			path:       path,
   633  			fetchError: fetchError,
   634  			c:          c,
   635  		},
   636  	}
   637  }
   638  
   639  // datasource implements simplestreams.DataSource and overrides the Fetch
   640  // method for testing purposes.
   641  type dataSource struct {
   642  	simplestreams.DataSource
   643  
   644  	metaPath   string
   645  	path       string
   646  	fetchError string
   647  	c          *gc.C
   648  }
   649  
   650  // Fetch implements simplestreams.DataSource.
   651  func (ds *dataSource) Fetch(path string) (io.ReadCloser, string, error) {
   652  	ds.c.Assert(path, gc.Equals, ds.metaPath)
   653  	if ds.fetchError != "" {
   654  		return nil, "", errors.New(ds.fetchError)
   655  	}
   656  	f, err := os.Open(ds.path)
   657  	ds.c.Assert(err, jc.ErrorIsNil)
   658  	return f, "", nil
   659  }