github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/project_matrix.go (about)

     1  package model
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/evergreen-ci/evergreen/command"
     9  	"github.com/evergreen-ci/evergreen/util"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // This file contains the code for matrix generation.
    14  // Project matrices are a shortcut for defining many variants
    15  // by combining multiple axes. A full explanation of the matrix format
    16  // is available at #TODO GitHub wiki documentation (EVG-1175).
    17  //
    18  // On a high level, matrix variant construction takes the following steps:
    19  //   1. Matrix definitions are read as part of a project's `buildvariants` field
    20  //  and moved into a separate "matrices" slice.
    21  //   2. A tag selector evaluator is constructed for evaluating axis selectors
    22  //   3. The matrix and axis definitions are passed to buildMatrixVariants, which
    23  //  creates all combinations of matrix cells and removes excluded ones.
    24  //   4. During the generation of a single cell, we merge all axis values for the cell
    25  //  together to create a fully filled-in variant. Matrix rules concerning non-task settings
    26  //  are evaluated as well. Rules `add_tasks` and `remove_tasks` are stored in the variant
    27  //  for later evaluation.
    28  //   5. Created variants are appended back to the project's list of buildvariants.
    29  //   6. During evaluateBuildVariants in project_parser.go, rules are executed.
    30  
    31  // matrix defines a set of variants programmatically by
    32  // combining a series of axis values and rules.
    33  type matrix struct {
    34  	Id          string            `yaml:"matrix_name"`
    35  	Spec        matrixDefinition  `yaml:"matrix_spec"`
    36  	Exclude     matrixDefinitions `yaml:"exclude_spec"`
    37  	DisplayName string            `yaml:"display_name"`
    38  	Tags        parserStringSlice `yaml:"tags"`
    39  	Modules     parserStringSlice `yaml:"modules"`
    40  	BatchTime   *int              `yaml:"batchtime"`
    41  	Stepback    *bool             `yaml:"stepback"`
    42  	RunOn       parserStringSlice `yaml:"run_on"`
    43  	Tasks       parserBVTasks     `yaml:"tasks"`
    44  	Rules       []matrixRule      `yaml:"rules"`
    45  }
    46  
    47  // matrixAxis represents one axis of a matrix definition.
    48  type matrixAxis struct {
    49  	Id          string      `yaml:"id"`
    50  	DisplayName string      `yaml:"display_name"`
    51  	Values      []axisValue `yaml:"values"`
    52  }
    53  
    54  // find returns the axisValue with the given name.
    55  func (ma matrixAxis) find(id string) (axisValue, error) {
    56  	for _, v := range ma.Values {
    57  		if v.Id == id {
    58  			return v, nil
    59  		}
    60  	}
    61  	return axisValue{}, errors.Errorf("axis '%v' does not contain value '%v'", ma.Id, id)
    62  }
    63  
    64  // axisValues make up the "points" along a matrix axis. Values are
    65  // combined during matrix evaluation to produce new variants.
    66  type axisValue struct {
    67  	Id          string             `yaml:"id"`
    68  	DisplayName string             `yaml:"display_name"`
    69  	Variables   command.Expansions `yaml:"variables"`
    70  	RunOn       parserStringSlice  `yaml:"run_on"`
    71  	Tags        parserStringSlice  `yaml:"tags"`
    72  	Modules     parserStringSlice  `yaml:"modules"`
    73  	BatchTime   *int               `yaml:"batchtime"`
    74  	Stepback    *bool              `yaml:"stepback"`
    75  }
    76  
    77  // helper methods for tag selectors
    78  func (av *axisValue) name() string   { return av.Id }
    79  func (av *axisValue) tags() []string { return av.Tags }
    80  
    81  // matrixValue represents a "cell" of a matrix
    82  type matrixValue map[string]string
    83  
    84  // String returns the matrixValue in simple JSON format
    85  func (mv matrixValue) String() string {
    86  	asJSON, err := json.Marshal(&mv)
    87  	if err != nil {
    88  		return fmt.Sprintf("%#v", mv)
    89  	}
    90  	return string(asJSON)
    91  }
    92  
    93  // matrixDefinition is a map of axis name -> axis value representing
    94  // an n-dimensional matrix configuration.
    95  type matrixDefinition map[string]parserStringSlice
    96  
    97  // String returns the matrixDefinition in simple JSON format
    98  func (mdef matrixDefinition) String() string {
    99  	asJSON, err := json.Marshal(&mdef)
   100  	if err != nil {
   101  		return fmt.Sprintf("%#v", mdef)
   102  	}
   103  	return string(asJSON)
   104  }
   105  
   106  // allCells returns every value (cell) within the matrix definition.
   107  // IMPORTANT: this logic assume that all selectors have been evaluated
   108  // and no duplicates exist.
   109  func (mdef matrixDefinition) allCells() []matrixValue {
   110  	// this should never happen, we handle empty defs but just for sanity
   111  	if len(mdef) == 0 {
   112  		return nil
   113  	}
   114  	// You can think of the logic below as traversing an n-dimensional matrix,
   115  	// emulating an n-dimensional for-loop using a set of counters (like an old-school
   116  	// golf counter).  We're doing this iteratively to avoid the overhead and sloppy code
   117  	// required to constantly copy and merge maps that using recursion would require.
   118  	type axisCache struct {
   119  		Id    string
   120  		Vals  []string
   121  		Count int
   122  	}
   123  	axes := []axisCache{}
   124  	for axis, values := range mdef {
   125  		if len(values) == 0 {
   126  			panic(fmt.Sprintf("axis '%v' has empty values list", axis))
   127  		}
   128  		axes = append(axes, axisCache{Id: axis, Vals: values})
   129  	}
   130  	carryOne := false
   131  	cells := []matrixValue{}
   132  	for {
   133  		c := matrixValue{}
   134  		for i := range axes {
   135  			if carryOne {
   136  				carryOne = false
   137  				axes[i].Count = (axes[i].Count + 1) % len(axes[i].Vals)
   138  				if axes[i].Count == 0 { // we overflowed--time to carry the one
   139  					carryOne = true
   140  				}
   141  			}
   142  			// set the current axis/value pair for the new cell
   143  			c[axes[i].Id] = axes[i].Vals[axes[i].Count]
   144  		}
   145  		// if carryOne is still true, that means the final bucket overflowed--we've finished.
   146  		if carryOne {
   147  			break
   148  		}
   149  		cells = append(cells, c)
   150  		// add one to the leftmost bucket on the next loop
   151  		carryOne = true
   152  	}
   153  	return cells
   154  }
   155  
   156  // evaluatedCopy returns a copy of the definition with its tag selectors evaluated.
   157  func (mdef matrixDefinition) evaluatedCopy(ase *axisSelectorEvaluator) (matrixDefinition, []error) {
   158  	var errs []error
   159  	cpy := matrixDefinition{}
   160  	for axis, vals := range mdef {
   161  		evaluated, evalErrs := evaluateAxisTags(ase, axis, vals)
   162  		if len(evalErrs) > 0 {
   163  			errs = append(errs, evalErrs...)
   164  			continue
   165  		}
   166  		cpy[axis] = evaluated
   167  	}
   168  	return cpy, errs
   169  }
   170  
   171  // contains returns whether a value is contained by a definition.
   172  // Note that a value that doesn't contain every matrix axis will still
   173  // be evaluated based on the axes that exist.
   174  func (mdef matrixDefinition) contains(mv matrixValue) bool {
   175  	for k, v := range mv {
   176  		axis, ok := mdef[k]
   177  		if !ok {
   178  			return false
   179  		}
   180  		if !util.SliceContains(axis, v) {
   181  			return false
   182  		}
   183  	}
   184  	return true
   185  }
   186  
   187  // matrixDefintinos is a helper type for parsing either a single definition
   188  // or a slice of definitions from YAML.
   189  type matrixDefinitions []matrixDefinition
   190  
   191  // UnmarshalYAML allows the YAML parser to read both a single def or
   192  // an array of them into a slice.
   193  func (mds *matrixDefinitions) UnmarshalYAML(unmarshal func(interface{}) error) error {
   194  	var single matrixDefinition
   195  	if err := unmarshal(&single); err == nil {
   196  		*mds = matrixDefinitions{single}
   197  		return nil
   198  	}
   199  	var slice []matrixDefinition
   200  	if err := unmarshal(&slice); err != nil {
   201  		return err
   202  	}
   203  	*mds = slice
   204  	return nil
   205  }
   206  
   207  // contain returns true if *any* of the definitions contain the given value.
   208  func (mds matrixDefinitions) contain(v matrixValue) bool {
   209  	for _, m := range mds {
   210  		if m.contains(v) {
   211  			return true
   212  		}
   213  	}
   214  	return false
   215  }
   216  
   217  // evaluatedCopies is like evaluatedCopy, but for multiple definitions.
   218  func (mds matrixDefinitions) evaluatedCopies(ase *axisSelectorEvaluator) (matrixDefinitions, []error) {
   219  	var out matrixDefinitions
   220  	var errs []error
   221  	for _, md := range mds {
   222  		evaluated, evalErrs := md.evaluatedCopy(ase)
   223  		errs = append(errs, evalErrs...)
   224  		out = append(out, evaluated)
   225  	}
   226  	return out, errs
   227  }
   228  
   229  // evaluateAxisTags returns an evaluated list of axis value ids with tag selectors evaluated.
   230  func evaluateAxisTags(ase *axisSelectorEvaluator, axis string, selectors []string) ([]string, []error) {
   231  	var errs []error
   232  	all := map[string]struct{}{}
   233  	for _, s := range selectors {
   234  		ids, err := ase.evalSelector(axis, ParseSelector(s))
   235  		if err != nil {
   236  			errs = append(errs, err)
   237  			continue
   238  		}
   239  		for _, id := range ids {
   240  			all[id] = struct{}{}
   241  		}
   242  	}
   243  	out := []string{}
   244  	for id, _ := range all {
   245  		out = append(out, id)
   246  	}
   247  	return out, errs
   248  }
   249  
   250  // buildMatrixVariants takes in a list of axis definitions, an axisSelectorEvaluator, and a slice of
   251  // matrix definitions. It returns a slice of parserBuildVariants constructed according to
   252  // our matrix specification.
   253  func buildMatrixVariants(axes []matrixAxis, ase *axisSelectorEvaluator, matrices []matrix) (
   254  	[]parserBV, []error) {
   255  	var errs []error
   256  	// for each matrix, build out its declarations
   257  	matrixVariants := []parserBV{}
   258  	for i, m := range matrices {
   259  		// for each axis value, iterate through possible inputs
   260  		evaluatedSpec, evalErrs := m.Spec.evaluatedCopy(ase)
   261  		if len(evalErrs) > 0 {
   262  			errs = append(errs, evalErrs...)
   263  			continue
   264  		}
   265  		evaluatedExcludes, evalErrs := m.Exclude.evaluatedCopies(ase)
   266  		if len(evalErrs) > 0 {
   267  			errs = append(errs, evalErrs...)
   268  			continue
   269  		}
   270  		unpruned := evaluatedSpec.allCells()
   271  		pruned := []parserBV{}
   272  		for _, cell := range unpruned {
   273  			// create the variant if it isn't excluded
   274  			if !evaluatedExcludes.contain(cell) {
   275  				v, err := buildMatrixVariant(axes, cell, &matrices[i], ase)
   276  				if err != nil {
   277  					errs = append(errs, errors.Wrapf(err, "%v: error building matrix cell %v",
   278  						m.Id, cell))
   279  					continue
   280  				}
   281  				pruned = append(pruned, *v)
   282  			}
   283  		}
   284  		// safety check to make sure the exclude field is actually working
   285  		if len(m.Exclude) > 0 && len(unpruned) == len(pruned) {
   286  			errs = append(errs, errors.Errorf("%v: exclude field did not exclude anything", m.Id))
   287  		}
   288  		matrixVariants = append(matrixVariants, pruned...)
   289  	}
   290  	return matrixVariants, errs
   291  }
   292  
   293  // buildMatrixVariant does the heavy lifting of building a matrix variant based on axis information.
   294  // We do this by iterating over all axes and merging the axis value's settings when applicable. Expansions
   295  // are evaluated during this process. Rules are parsed and added to the resulting parserBV for later
   296  // execution.
   297  func buildMatrixVariant(axes []matrixAxis, mv matrixValue, m *matrix, ase *axisSelectorEvaluator) (*parserBV, error) {
   298  	v := parserBV{
   299  		matrixVal:  mv,
   300  		matrixId:   m.Id,
   301  		Stepback:   m.Stepback,
   302  		BatchTime:  m.BatchTime,
   303  		Modules:    m.Modules,
   304  		RunOn:      m.RunOn,
   305  		Expansions: *command.NewExpansions(mv),
   306  	}
   307  	// we declare a separate expansion map for evaluating the display name
   308  	displayNameExp := command.Expansions{}
   309  
   310  	// build up the variant id while iterating through axis values
   311  	idBuf := bytes.Buffer{}
   312  	idBuf.WriteString(m.Id)
   313  	idBuf.WriteString("__")
   314  
   315  	// track how many axes we cover, so we know the value is only using real axes
   316  	usedAxes := 0
   317  
   318  	// we must iterate over axis definitions to have a consistent ordering for our axis priority
   319  	for _, a := range axes {
   320  		// skip any axes that aren't used in the variant's definition
   321  		if _, ok := mv[a.Id]; !ok {
   322  			continue
   323  		}
   324  		usedAxes++
   325  		axisVal, err := a.find(mv[a.Id])
   326  		if err != nil {
   327  			return nil, err
   328  		}
   329  		if err := v.mergeAxisValue(axisVal); err != nil {
   330  			return nil, errors.Wrapf(err, "processing axis value %v, %v", a.Id, axisVal.Id)
   331  		}
   332  		// for display names, fall back to the axis values id so we have *something*
   333  		if axisVal.DisplayName != "" {
   334  			displayNameExp.Put(a.Id, axisVal.DisplayName)
   335  		} else {
   336  			displayNameExp.Put(a.Id, axisVal.Id)
   337  		}
   338  
   339  		// append to the variant's name
   340  		idBuf.WriteString(a.Id)
   341  		idBuf.WriteRune('~')
   342  		idBuf.WriteString(axisVal.Id)
   343  		if usedAxes < len(mv) {
   344  			idBuf.WriteRune('_')
   345  		}
   346  	}
   347  	if usedAxes != len(mv) {
   348  		// we could make this error more helpful at the expense of extra complexity
   349  		return nil, errors.Errorf("cell %v uses undefined axes", mv)
   350  	}
   351  	v.Name = idBuf.String()
   352  	disp, err := displayNameExp.ExpandString(m.DisplayName)
   353  	if err != nil {
   354  		return nil, errors.Wrap(err, "processing display name")
   355  	}
   356  	v.DisplayName = disp
   357  
   358  	// add final matrix-level tags and tasks
   359  	if err := v.mergeAxisValue(axisValue{Tags: m.Tags}); err != nil {
   360  		return nil, errors.Wrap(err, "processing matrix tags")
   361  	}
   362  	for _, t := range m.Tasks {
   363  		expTask, err := expandParserBVTask(t, v.Expansions)
   364  		if err != nil {
   365  			return nil, errors.Wrapf(err, "processing task %s", t.Name)
   366  		}
   367  		v.Tasks = append(v.Tasks, expTask)
   368  	}
   369  
   370  	// evaluate rules for matching matrix values
   371  	for i, rule := range m.Rules {
   372  		r, err := expandRule(rule, v.Expansions)
   373  		if err != nil {
   374  			return nil, errors.Wrapf(err, "processing rule[%d]", i)
   375  		}
   376  		matchers, errs := r.If.evaluatedCopies(ase) // we could cache this
   377  		if len(errs) > 0 {
   378  			return nil, errors.Errorf("evaluating rules for matrix %v: %v", m.Id, errs)
   379  		}
   380  		if matchers.contain(mv) {
   381  			if r.Then.Set != nil {
   382  				if err := v.mergeAxisValue(*r.Then.Set); err != nil {
   383  					return nil, errors.Wrapf(err, "evaluating %s rule %d", m.Id, i)
   384  				}
   385  			}
   386  			// we append add/remove task rules internally and execute them
   387  			// during task evaluation, when other tasks are being evaluated.
   388  			if len(r.Then.RemoveTasks) > 0 || len(r.Then.AddTasks) > 0 {
   389  				v.matrixRules = append(v.matrixRules, r.Then)
   390  			}
   391  		}
   392  	}
   393  	return &v, nil
   394  }
   395  
   396  // matrixRule allows users to manipulate arbitrary matrix values using selectors.
   397  type matrixRule struct {
   398  	If   matrixDefinitions `yaml:"if"`
   399  	Then ruleAction        `yaml:"then"`
   400  }
   401  
   402  // ruleAction is used to define what work must be done when
   403  // "matrixRule.If" is satisfied.
   404  type ruleAction struct {
   405  	Set         *axisValue        `yaml:"set"`
   406  	RemoveTasks parserStringSlice `yaml:"remove_tasks"`
   407  	AddTasks    parserBVTasks     `yaml:"add_tasks"`
   408  }
   409  
   410  // mergeAxisValue overwrites a parserBV's fields based on settings
   411  // in the axis value. Matrix expansions are evaluated as this process occurs.
   412  // Returns any errors evaluating expansions.
   413  func (pbv *parserBV) mergeAxisValue(av axisValue) error {
   414  	// expand the variant's expansions (woah, dude) and update them
   415  	if len(av.Variables) > 0 {
   416  		expanded, err := expandExpansions(av.Variables, pbv.Expansions)
   417  		if err != nil {
   418  			return errors.Wrap(err, "expanding variables")
   419  		}
   420  		pbv.Expansions.Update(expanded)
   421  	}
   422  	// merge tags, removing dupes
   423  	if len(av.Tags) > 0 {
   424  		expanded, err := expandStrings(av.Tags, pbv.Expansions)
   425  		if err != nil {
   426  			return errors.Wrap(err, "expanding tags")
   427  		}
   428  		pbv.Tags = util.UniqueStrings(append(pbv.Tags, expanded...))
   429  	}
   430  	// overwrite run_on
   431  	var err error
   432  	if len(av.RunOn) > 0 {
   433  		pbv.RunOn, err = expandStrings(av.RunOn, pbv.Expansions)
   434  		if err != nil {
   435  			return errors.Wrap(err, "expanding run_on")
   436  		}
   437  	}
   438  	// overwrite modules
   439  	if len(av.Modules) > 0 {
   440  		pbv.Modules, err = expandStrings(av.Modules, pbv.Expansions)
   441  		if err != nil {
   442  			return errors.Wrap(err, "expanding modules")
   443  		}
   444  	}
   445  	if av.Stepback != nil {
   446  		pbv.Stepback = av.Stepback
   447  	}
   448  	if av.BatchTime != nil {
   449  		pbv.BatchTime = av.BatchTime
   450  	}
   451  	return nil
   452  }
   453  
   454  // expandStrings expands a slice of strings.
   455  func expandStrings(strings []string, exp command.Expansions) ([]string, error) {
   456  	var expanded []string
   457  	for _, s := range strings {
   458  		newS, err := exp.ExpandString(s)
   459  		if err != nil {
   460  			return nil, errors.WithStack(err)
   461  		}
   462  		expanded = append(expanded, newS)
   463  	}
   464  	return expanded, nil
   465  }
   466  
   467  // expandExpansions expands expansion maps.
   468  func expandExpansions(in, exp command.Expansions) (command.Expansions, error) {
   469  	newExp := command.Expansions{}
   470  	for k, v := range in {
   471  		newK, err := exp.ExpandString(k)
   472  		if err != nil {
   473  			return nil, errors.WithStack(err)
   474  		}
   475  		newV, err := exp.ExpandString(v)
   476  		if err != nil {
   477  			return nil, errors.WithStack(err)
   478  		}
   479  		newExp[newK] = newV
   480  	}
   481  	return newExp, nil
   482  }
   483  
   484  // expandParserBVTask expands strings inside parserBVTs.
   485  func expandParserBVTask(pbvt parserBVTask, exp command.Expansions) (parserBVTask, error) {
   486  	var err error
   487  	newTask := pbvt
   488  	newTask.Name, err = exp.ExpandString(pbvt.Name)
   489  	if err != nil {
   490  		return parserBVTask{}, errors.Wrap(err, "expanding name")
   491  	}
   492  	newTask.RunOn, err = expandStrings(pbvt.RunOn, exp)
   493  	if err != nil {
   494  		return parserBVTask{}, errors.Wrap(err, "expanding run_on")
   495  	}
   496  	newTask.Distros, err = expandStrings(pbvt.Distros, exp)
   497  	if err != nil {
   498  		return parserBVTask{}, errors.Wrap(err, "expanding distros")
   499  	}
   500  	var newDeps parserDependencies
   501  	for i, d := range pbvt.DependsOn {
   502  		newDep := d
   503  		newDep.Status, err = exp.ExpandString(d.Status)
   504  		if err != nil {
   505  			return parserBVTask{}, errors.Wrapf(err, "expanding depends_on[%d/%d].status", i, len(pbvt.DependsOn))
   506  		}
   507  		newDep.taskSelector, err = expandTaskSelector(d.taskSelector, exp)
   508  		if err != nil {
   509  			return parserBVTask{}, errors.Wrapf(err, "expanding depends_on[%d/%d]", i, len(pbvt.DependsOn))
   510  		}
   511  		newDeps = append(newDeps, newDep)
   512  	}
   513  	newTask.DependsOn = newDeps
   514  	var newReqs taskSelectors
   515  	for i, r := range pbvt.Requires {
   516  		newReq, err := expandTaskSelector(r, exp)
   517  		if err != nil {
   518  			return parserBVTask{}, errors.Wrapf(err, "expanding requires[%d/%d]", i, len(pbvt.Requires))
   519  		}
   520  		newReqs = append(newReqs, newReq)
   521  	}
   522  	newTask.Requires = newReqs
   523  	return newTask, nil
   524  }
   525  
   526  // expandTaskSelector expands strings inside task selectors.
   527  func expandTaskSelector(ts taskSelector, exp command.Expansions) (taskSelector, error) {
   528  	newTS := taskSelector{}
   529  	newName, err := exp.ExpandString(ts.Name)
   530  	if err != nil {
   531  		return newTS, errors.Wrap(err, "expanding name")
   532  	}
   533  	newTS.Name = newName
   534  	if v := ts.Variant; v != nil {
   535  		if len(v.matrixSelector) > 0 {
   536  			newMS, err := expandMatrixDefinition(v.matrixSelector, exp)
   537  			if err != nil {
   538  				return newTS, errors.Wrap(err, "expanding variant")
   539  			}
   540  			newTS.Variant = &variantSelector{
   541  				matrixSelector: newMS,
   542  			}
   543  		} else {
   544  			selector, err := exp.ExpandString(v.stringSelector)
   545  			if err != nil {
   546  				return newTS, errors.Wrap(err, "expanding variant")
   547  			}
   548  			newTS.Variant = &variantSelector{
   549  				stringSelector: selector,
   550  			}
   551  		}
   552  	}
   553  	return newTS, nil
   554  }
   555  
   556  // expandMatrixDefinition expands strings inside matrix definitions.
   557  func expandMatrixDefinition(md matrixDefinition, exp command.Expansions) (matrixDefinition, error) {
   558  	var err error
   559  	newMS := matrixDefinition{}
   560  	for axis, vals := range md {
   561  		newMS[axis], err = expandStrings(vals, exp)
   562  		if err != nil {
   563  			return nil, errors.Wrap(err, "matrix selector")
   564  		}
   565  	}
   566  	return newMS, nil
   567  }
   568  
   569  // expandRules expands strings inside of rules.
   570  func expandRule(r matrixRule, exp command.Expansions) (matrixRule, error) {
   571  	newR := matrixRule{}
   572  	for _, md := range r.If {
   573  		newIf, err := expandMatrixDefinition(md, exp)
   574  		if err != nil {
   575  			return newR, errors.Wrap(err, "if")
   576  		}
   577  		newR.If = append(newR.If, newIf)
   578  	}
   579  	for _, t := range r.Then.AddTasks {
   580  		newTask, err := expandParserBVTask(t, exp)
   581  		if err != nil {
   582  			return newR, errors.Wrap(err, "add_tasks")
   583  		}
   584  		newR.Then.AddTasks = append(newR.Then.AddTasks, newTask)
   585  	}
   586  	if len(r.Then.RemoveTasks) > 0 {
   587  		var err error
   588  		newR.Then.RemoveTasks, err = expandStrings(r.Then.RemoveTasks, exp)
   589  		if err != nil {
   590  			return newR, errors.Wrap(err, "remove_tasks")
   591  		}
   592  	}
   593  	// r.Then.Set will be taken care of when mergeAxisValue is called
   594  	// so we don't have to do it in this function
   595  	return newR, nil
   596  }